-
Notifications
You must be signed in to change notification settings - Fork 7.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix for RxJava 0.18.+ #468
Closed
Closed
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
9488e86
added inner RetrofitScheduler
chrisjenx 7e3bebc
removed unused imports
chrisjenx 966254b
checkstyle fixes
chrisjenx a048035
migrated Scheduler into the Schedulers class for shared usage.
chrisjenx ee29804
checkstyle
chrisjenx 53fee10
cleaned up comments as-per feedback.
chrisjenx 37ef1a3
Comment to end-of-line style
chrisjenx f584287
fix checkstyle
chrisjenx a350444
promoted RxSupport to retrofit.
chrisjenx 17360ca
as per @jhump's suggestions simplified executing a task. passed to th…
chrisjenx e009ea8
Comments cleanup
chrisjenx 2765e40
Moved away from Scheduler and Execute inside OnSubscribe.
chrisjenx 2c19f42
Fixed MockRestAdapter to mimic new OnSubscribe, added RxSupportTests
chrisjenx 97db5c9
comment clean up
chrisjenx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package retrofit; | ||
|
||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.FutureTask; | ||
|
||
import rx.Observable; | ||
import rx.Subscriber; | ||
import rx.Subscription; | ||
import rx.subscriptions.Subscriptions; | ||
|
||
/** | ||
* Utilities for supporting RxJava Observables. | ||
* Used primarily by {@link retrofit.RestAdapter}. | ||
* | ||
* Remember RxJava might not be on the classpath, check its included before calling, use | ||
* {@link Platform#HAS_RX_JAVA} | ||
*/ | ||
final class RxSupport { | ||
private final Executor executor; | ||
private final ErrorHandler errorHandler; | ||
|
||
RxSupport(Executor executor, ErrorHandler errorHandler) { | ||
this.executor = executor; | ||
this.errorHandler = errorHandler; | ||
} | ||
|
||
Observable createRequestObservable(final Callable<ResponseWrapper> request) { | ||
return Observable.create(new Observable.OnSubscribe<Object>() { | ||
@Override public void call(Subscriber<? super Object> subscriber) { | ||
if (subscriber.isUnsubscribed()) { | ||
return; | ||
} | ||
final FutureTask<Void> task = new FutureTask<Void>(getRunnable(subscriber, request), null); | ||
final Subscription s = Subscriptions.from(task); | ||
// We add our subscription to the current subscriber so the future task can be | ||
// unSubscribed from. | ||
subscriber.add(s); | ||
executor.execute(task); | ||
} | ||
}); | ||
} | ||
|
||
private Runnable getRunnable(final Subscriber<? super Object> subscriber, | ||
final Callable<ResponseWrapper> request) { | ||
return new Runnable() { | ||
@Override public void run() { | ||
try { | ||
if (subscriber.isUnsubscribed()) { | ||
return; | ||
} | ||
ResponseWrapper wrapper = request.call(); | ||
subscriber.onNext(wrapper.responseBody); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should change this method so that |
||
subscriber.onCompleted(); | ||
} catch (RetrofitError e) { | ||
subscriber.onError(errorHandler.handleError(e)); | ||
} catch (Exception e) { | ||
// This is from the Callable. It shouldn't actually throw. | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package retrofit; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.mockito.Mock; | ||
import org.mockito.MockitoAnnotations; | ||
|
||
import java.util.ArrayDeque; | ||
import java.util.Collections; | ||
import java.util.Deque; | ||
import java.util.Iterator; | ||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import retrofit.client.Header; | ||
import retrofit.client.Response; | ||
import retrofit.mime.TypedInput; | ||
import rx.Observer; | ||
import rx.Subscription; | ||
import rx.schedulers.Schedulers; | ||
import rx.schedulers.TestScheduler; | ||
|
||
import static org.mockito.Matchers.any; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.never; | ||
import static org.mockito.Mockito.spy; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
|
||
public class RxSupportTest { | ||
|
||
private Object response; | ||
private ResponseWrapper responseWrapper; | ||
private Callable<ResponseWrapper> callable = spy(new Callable<ResponseWrapper>() { | ||
@Override public ResponseWrapper call() throws Exception { | ||
return responseWrapper; | ||
} | ||
}); | ||
|
||
private QueuedSynchronousExecutor executor; | ||
private ErrorHandler errorHandler; | ||
private RxSupport rxSupport; | ||
|
||
@Before | ||
public void setUp() throws Exception { | ||
MockitoAnnotations.initMocks(this); | ||
response = new Object(); | ||
responseWrapper = new ResponseWrapper( | ||
new Response( | ||
"http://example.com", 200, "Success", | ||
Collections.<Header>emptyList(), mock(TypedInput.class) | ||
), response | ||
); | ||
executor = spy(new QueuedSynchronousExecutor()); | ||
errorHandler = ErrorHandler.DEFAULT; | ||
rxSupport = new RxSupport(executor, errorHandler); | ||
} | ||
|
||
@Mock | ||
Observer<Object> subscriber; | ||
|
||
@Test | ||
public void testObservableCallsOnNextOnHttpExecutor() throws Exception { | ||
rxSupport.createRequestObservable(callable).subscribe(subscriber); | ||
executor.executeNextInQueue(); | ||
verify(subscriber, times(1)).onNext(response); | ||
} | ||
|
||
@Test | ||
public void testObservableCallsOnNextOnHttpExecutorWithSubscriber() throws Exception { | ||
TestScheduler test = Schedulers.test(); | ||
rxSupport.createRequestObservable(callable).subscribeOn(test).subscribe(subscriber); | ||
// Subscription is handled via the Scheduler. | ||
test.triggerActions(); | ||
// This will only execute up to the executor in OnSubscribe. | ||
verify(subscriber, never()).onNext(any()); | ||
// Upon continuing the executor we then run the retrofit request. | ||
executor.executeNextInQueue(); | ||
verify(subscriber, times(1)).onNext(response); | ||
} | ||
|
||
@Test | ||
public void testObservableUnSubscribesDoesNotExecuteCallable() throws Exception { | ||
Subscription subscription = rxSupport.createRequestObservable(callable).subscribe(subscriber); | ||
verify(subscriber, never()).onNext(any()); | ||
|
||
// UnSubscribe here should cancel the queued runnable. | ||
subscription.unsubscribe(); | ||
|
||
executor.executeNextInQueue(); | ||
verify(callable, never()).call(); | ||
verify(subscriber, never()).onNext(response); | ||
} | ||
|
||
@Test | ||
public void testObservableCallsOperatorsOffHttpExecutor() throws Exception { | ||
TestScheduler test = Schedulers.test(); | ||
rxSupport.createRequestObservable(callable) | ||
.delaySubscription(1000, TimeUnit.MILLISECONDS, test) | ||
.subscribe(subscriber); | ||
|
||
verify(subscriber, never()).onNext(any()); | ||
test.advanceTimeBy(1000, TimeUnit.MILLISECONDS); | ||
// Upon continuing the executor we then run the retrofit request. | ||
executor.executeNextInQueue(); | ||
verify(subscriber, times(1)).onNext(response); | ||
} | ||
|
||
@Test | ||
public void testObservableDoesNotLockExecutor() throws Exception { | ||
TestScheduler test = Schedulers.test(); | ||
Subscription subscription1 = rxSupport.createRequestObservable(callable) | ||
.delay(1000, TimeUnit.MILLISECONDS, test) | ||
.subscribe(subscriber); | ||
|
||
Subscription subscription2 = rxSupport.createRequestObservable(callable) | ||
.delay(2000, TimeUnit.MILLISECONDS, test) | ||
.subscribe(subscriber); | ||
|
||
// Nothing fired yet | ||
verify(subscriber, never()).onNext(any()); | ||
// Subscriptions should of been queued up and executed even tho we delayed on the Subscriber. | ||
executor.executeNextInQueue(); | ||
executor.executeNextInQueue(); | ||
|
||
verify(subscriber, never()).onNext(response); | ||
|
||
test.advanceTimeBy(1000, TimeUnit.MILLISECONDS); | ||
verify(subscriber, times(1)).onNext(response); | ||
|
||
test.advanceTimeBy(1000, TimeUnit.MILLISECONDS); | ||
verify(subscriber, times(2)).onNext(response); | ||
} | ||
|
||
@Test | ||
public void testObservableRespectsObserveOn() throws Exception { | ||
TestScheduler observe = Schedulers.test(); | ||
rxSupport.createRequestObservable(callable) | ||
.observeOn(observe) | ||
.subscribe(subscriber); | ||
|
||
verify(subscriber, never()).onNext(any()); | ||
executor.executeNextInQueue(); | ||
|
||
// Should have no response yet, but callback should of been executed. | ||
verify(subscriber, never()).onNext(any()); | ||
verify(callable, times(1)).call(); | ||
|
||
// Forward the Observable Scheduler | ||
observe.triggerActions(); | ||
verify(subscriber, times(1)).onNext(response); | ||
} | ||
|
||
/** | ||
* Test Executor to iterate through Executions to aid in checking | ||
* that the Observable implementation is correct. | ||
*/ | ||
static class QueuedSynchronousExecutor implements Executor { | ||
Deque<Runnable> runnableQueue = new ArrayDeque<Runnable>(); | ||
|
||
@Override public void execute(Runnable runnable) { | ||
runnableQueue.add(runnable); | ||
} | ||
|
||
/** | ||
* Will throw exception if you are expecting something to be added to the Executor | ||
* and it hasn't. | ||
*/ | ||
void executeNextInQueue() { | ||
runnableQueue.removeFirst().run(); | ||
} | ||
|
||
/** | ||
* Executes any queued executions on the executor. | ||
*/ | ||
void executeAll() { | ||
Iterator<Runnable> iterator = runnableQueue.iterator(); | ||
while (iterator.hasNext()) { | ||
Runnable next = iterator.next(); | ||
next.run(); | ||
iterator.remove(); | ||
} | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to support cancelation, you can capture the
Future
from this and register it with theSubscriber
.For example:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is the
MockRestAdapter
so it probably doesn't matter ...