Chaining Retrofit services w/ RxJava support - android

I am having trouble with chaining observables using retrofit's RxJava support. I'm probably misunderstanding how to use it, otherwise it could be a bug in retrofit. Hopefully someone here can help me understand what's going on. Edit: I am using the MockRestAdapter for these responses - this might be relevant as I see the RxSupport implementations differ slightly.
This is a fake banking app. It's trying to do a transfer, and after the transfer is completed, then it should do a accounts request to update the account values. This is basically just an excuse for me to try out flatMap. The following code unfortunately doesn't work, no subscribers ever get notified:
Case 1: chaining two retrofit-produced observables
The transfer service (note: returns a retrofit-produced observable):
#FormUrlEncoded #POST("/user/transactions/")
public Observable<TransferResponse> transfer(#Field("session_id") String sessionId,
#Field("from_account_number") String fromAccountNumber,
#Field("to_account_number") String toAccountNumber,
#Field("amount") String amount);
The account service (note: returns a retrofit-produced observable):
#FormUrlEncoded #POST("/user/accounts")
public Observable<List<Account>> getAccounts(#Field("session_id") String sessionId);
Chains two retrofit-produced observables together:
transfersService.transfer(session.getSessionId(), fromAccountNumber, toAccountNumber, amount)
.flatMap(new Func1<TransferResponse, Observable<? extends List<Account>>>() {
#Override public Observable<? extends List<Account>> call(TransferResponse transferResponse) {
return accountsService.getAccounts(session.getSessionId());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Case 2: creating my own observable and chaining with a retrofit-produced one
If I ignore the built in Rx support in Retrofit for the "flat mapped" call, it works perfectly! All subscribers get notified. See below:
The new accounts service (note: does not produce an observable):
#FormUrlEncoded #POST("/user/accounts")
public List<Account> getAccountsBlocking(#Field("session_id") String sessionId);
Create my own observable and emit the items myself:
transfersService.transfer(session.getSessionId(), fromAccountNumber, toAccountNumber, amount)
.flatMap(new Func1<TransferResponse, Observable<? extends List<Account>>>() {
#Override public Observable<? extends List<Account>> call(TransferResponse transferResponse) {
return Observable.create(new Observable.OnSubscribe<List<Account>>() {
#Override public void call(Subscriber<? super List<Account>> subscriber) {
List<Account> accounts = accountsService.getAccountsBlocking(session.getSessionId());
subscriber.onNext(accounts);
subscriber.onCompleted();
}
});
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Any help would be greatly appreciated!

The answer is yes you should be able to chain observables from Retrofit. There seems to be a bug in the MockRestAdapter$MockRxSupport:createMockObservable private class. The way the scheduling is done with respect to subscribing the subscriber to the observable seems wrong. Subscribing to the observable comes after in the HttpExecutor thread itself is started. I believe the original flow which comes from your Schedulers.io() thread is completed and unsubscribed before the mockHandler.invokeSync returned Observable can be subscribed to. Hopefully this explanation make some sort of sense if you take a look at the code in the retrofit-mock module.
As a workaround for now with the current code when using the retrofit-mock only you could substitute the internal default Executor with your own ImmediateExecutor implementation. This would allow at least when testing mocks to have a single thread flow which would be provided by your Schedulers.io.
// ImmediateExecutor.java
public class ImmediateExecutor implements Executor {
#Override
public void execute(Runnable command) {
command.run();
}
}
// Create your RestAdapter with your ImmdiateExecutor
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint(endpoint)
.setExecutors(new ImmediateExecutor(), null)
.build();
As for fixing the issue at the source you can also include the retrofit-mock project as source in your project and modify the MockRestAdapter$MockRxSupport:createMockObservable method using the code below. I've tested your use-case and it does fix the problem.
--- MockRestAdapter.java$MockRxSupport ----
Observable createMockObservable(final MockHandler mockHandler, final RestMethodInfo methodInfo,
final RequestInterceptor interceptor, final Object[] args) {
return Observable.create(new Observable.OnSubscribe<Object>() {
#Override public void call(final Subscriber<? super Object> subscriber) {
try {
if (subscriber.isUnsubscribed()) return;
Observable observable =
(Observable) mockHandler.invokeSync(methodInfo, interceptor, args);
observable.subscribeOn(Schedulers.from(httpExecutor));
//noinspection unchecked
observable.subscribe(subscriber);
} catch (RetrofitError e) {
subscriber.onError(errorHandler.handleError(e));
} catch (Throwable e) {
subscriber.onError(e);
}
}
});
}
Created an issue with the Retrofit project here, we'll see if they accept it.

Related

RxJava Android orientation change and network request

I'm new to RxJava and I need to integrate it into an existing project. I need to refactor the existing code, adding Observables for networking (Socket IO).
Currently when a network request is made (client -> server) a callback (interface) is added to a HashMap and once the request is completed, it will deliver the data back to the caller:
// Singleton
public class API {
public void checkTicket(String ticketId, final String networkRequestId, Callback callback) {
// Add the callback to the hashmap
registerCallback(networkRequestId, callback);
JSONObject json = RequestFactory.createTicketCheckerRequest(ticketId);
// Make the network request
getSocket().checkTicket(json, new Callback() {
#Override
public void onRequestDone(Response response) {
// Retrieve the callback
callback = getCallback(networkRequestId);
// Don't keep reference, remove from hashmap
unsubscribeCallback(networkRequestId);
// Check if it's unsuccessful and build the corresponding error response
if (!response.isSuccess()) {
// build custom error response
response = ResponseFactory.buildError(response);
}
// Deliver response from server
callback.onRequestDone(response);
}
});
}
}
It can be called from Activities and Fragments:
private void checkTicket() {
String ticketId = editText.getText().toString();
API.getInstance().checkTicket(ticketId, REQUEST_ID_CHECK_TICKET, new Callback() {
#Override
protected void onRequestDone(Response response) {
textView.setText(response.getData());
}
});
}
#Override
public void onDestroy() {
super.onDestroy();
// Removes callback from HashMap in case of the UI is destroyed before the arrives
API.getInstance().unsubscribe(REQUEST_ID_CHECK_TICKET);
}
The above code works but it's really tight with the UI's lifecycle and sometimes it's causing memory leak, because onDestroy() is not getting called (if you navigate between activities and Android OS kills the "paused" activities from the stack) or because the anonymous inner classes (callbacks) which are holding a reference to the UI, and from now on I need to support orientation change.
This is the code that I have implemented using RxJava:
API:
public Observable<Response> checkTicket(String ticketId) {
return Observable.create(subscriber -> {
JSONObject json = RequestFactory.createTicketCheckerRequest(ticketId);
// Make the network request
getSocket().checkTicket(json, new Callback() {
#Override
public void onRequestDone(Response response) {
subscriber.onNext(response);
subscriber.onComplete();
}
});
});
}
This is how it's called from the UI:
private CompositeDisposable mDisposables = new CompositeDisposable();
private void checkTicket() {
//////
Disposable disposable = API.getInstance().checkTicket(ticketId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(result -> {
textView.setText(result.getData());
});
mDisposables.add(disposable);
}
#Override
public void onStop() {
super.onStop();
if (!mDisposables.isDisposed()) {
mDisposables.dispose();
}
}
The above RxJava is working, however if an orientation change occurs the data is not returned because the Observer is unsubscribed.
Is the above implementation correct?
How should I subscribe without executing the request? Subscribe and wait for data change.
Another alternative would be EventBus but this is just Plan B. EventBus fits exactly my requirements, subscribe and wait for data change, but I want to evict boilerplate.
I have read other articles by using Fragment's setRetainInstance(true) but what if I need to use it from an Activity? What if I don't want to retain the state of the Fragment?
People suggested to use MVVM or MVP architecture, but I don't have the time to refactor the entire project.
I will suggest you move to MVVM. With your presented code, it is not that hard. Here is a sample code of how it will look like
Your ModelView
public class MyViewModel extends ViewModel {
private CompositeDisposable mDisposables = new CompositeDisposable();
private MutableLiveData<Response> response;
public LiveData<Response> getResponse() {
if (response == null) {
response = new MutableLiveData<Response>();
loadData();
}
return response;
}
private void loadData() {
Disposable disposable = API.getInstance().checkTicket(ticketId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(result -> {
response.postValue(result.getData());
});
mDisposables.add(disposable);
}
void onCleared()
{
super.onCleared();
mDisposables.clear(); //no more leaks. It takes care of lifecycle for you
}
}
Your Activity
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getResponse().observe(this, response -> {
// update UI
textView.setText(response); //response = Response object from Live data
});
}
}
If you don't want to handle configuration changes and want to cache data from observables, you can use BehaviorSubjects and a hot observable. This will allow you to get the most recent item that the observable published.
Other than that, I suggest you use the ViewModel from the architecture components. It will allow you to create a component that is bound to the activity but will not be affected by the lifecycle (except termination, obviously). Surprisingly enough, ViewModelProviders are implemented as fragments with setRetainInstance(true). You don't have to completely refactor the entire app. Just move the ones that you want to preserve during configuration changes.
You need to consider the logical scope of your network requests, and this is entirely separate from whether you're using RxJava. Background tasks like network requests need to be owned by an Android component (Application, Activity, etc.) with the appropriate lifetime. The usual way to make activity-scoped background tasks survive a config change is to host them in a retained fragment. You would still do that if you were using RxJava.
Android OS kills the "paused" activities from the stack
This doesn't happen unless something has changed in Android 8 or newer. The documentation suggests that the framework could destroy individual activities in the backstack, but currently it only destroys the entire task when it's in the background. Your app is correct and future-proof if and only if it works with the "don't keep activities" developer option on.

RxAndroid - Using Rx EventBus properly

Im exactly having this behavior
Subscriber OnComplete called twice
(which is is anticipated as per http://reactivex.io/documentation/subject.html)
But in my scenario : it goes something like this :
I have a AudioRecordingService which displays a notification, in which I have options for the user to save or delete the on going recording, which is working perfectly. But I'm trying to get into using RxAndroid, my notification's save button would trigger..
RxEventBus.getInstance().postEvent(new RxEvents(RxEventsEnum.AUDIO_STOP_AND_SAVE));
which triggers
bindUntilActivitySpecificEvent(RxEventBus.getInstance().forEventType(RxEvents.class),ActivityEvent.DESTROY).subscribeOn(
AndroidSchedulers.mainThread()).subscribe(new Action1<RxEvents>() {
#Override public void call(RxEvents rxEvents) {
onEvent(rxEvents);
}
});
and in my onEvent(rxEvent) based on the rxEvents object's data I appropriately save and store recording. The first time I try this, it works fine, but the subsequent times, the
#Override public void call(RxEvents rxEvents) {
onEvent(rxEvents);
}
is being called multiple times, like for example the second time I post an event, this callback is called twice, the third time thrice and so on... (which is actually what PublishSubject does). I don't want this behavior, I want Rx to be a able to post events and receive only the latest event that was posted and nothing else.
Here is my other relevant code
protected final <T> Observable<T> bindUntilActivitySpecificEvent(Observable<T> observable,
ActivityEvent event) {
return observable.compose(RxLifecycle.<T, ActivityEvent>bindUntilEvent(lifecycle(), event))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
and my run of the mill RxEventBus class :
public class RxEventBus {
private static final RxEventBus INSTANCE = new RxEventBus();
public static RxEventBus getInstance() {
return INSTANCE;
}
private RxEventBus() {
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void postEvent(Object event) {
mBus.onNext(event);
}
public <T> Observable<T> forEventType(Class<T> eventType) {
return mBus.ofType(eventType).observeOn(AndroidSchedulers.mainThread());
}
}
What is the best approach using RxAndroid ? Please note that I am looking for RxAndroid solution only.
You are creating a new observable every time you trigger an event in
RxEventBus.getInstance().forEventType(RxEvents.class)
You need to cache the observables you create for each event type.

How to create an Observable in Android?

What I want to do is to create a simple in-memory cache just to try Observables out. However I got stuck because I don't understand how to create an observable. This is the code I have gotten so far:
public class MovieCache {
MovieWrapper movieWrapper;
public Observable<MovieWrapper> getMovies() {
//How to create and return an Observable<MovieWrapper> here?
}
public void setCache(MovieWrapper wrapper) {
movieWrapper = wrapper;
}
public void clearCache() {
movieWrapper = null;
}
}
In the getMovies() method I want to create an Observable and return my local field movieWrapper to the subscriber. How can I do this? I tried with using new Observable.just(movieWrapper) but it results in a null exception.
Take a look at this tutorial as it does exactly what you are looking for. Basically you use defer() to make sure you always get the latest version of your cached object:
public class MovieCache {
MovieWrapper movieWrapper;
public Observable<MovieWrapper> getMovies() {
return Observable.defer(new Func0<Observable<MovieWrapper>>() {
#Override
public Observable<MovieWrapper> call() {
return Observable.just(movieWrapper);
}
});
}
public void setCache(MovieWrapper wrapper) {
movieWrapper = wrapper;
}
public void clearCache() {
movieWrapper = null;
}
}
defer() makes sure that you will get the object upon subscription to the Observable not on creation.
Note however that, according to the author of the post:
The only downside to defer() is that it creates a new Observable each
time you get a subscriber. create() can use the same function for each
subscriber, so it's more efficient. As always, measure performance and
optimize if necessary.
As already said, accepted answer has downside
it creates a new Observable each time you get a subscriber
But it is not the only one.
Consumer won't receive any value if he calls getMovies().subscribe(...) before setCache(...) is called.
Consumer should resubscribe if he want to receive any updates (let's say setCache() can be called multiple times.
Of course all of them can be irrelevant in your scenario. I just want to show you another way (I'm sure there are many more).
You can use BehaviorSubject in order to eliminate all these disadvantages.
public class MovieCache {
private BehaviorSubject<MovieWrapper> mMovieCache = BehaviorSubject.create();
public void setCache(MovieWrapper wrapper) {
mMovieCache.onNext(wrapper);
}
public Observable<MovieWrapper> getMovieObservable() {
//use this if consumer want to receive all updates
return mMovieCache.asObservable();
}
public MovieWrapper getMovie() {
//use this if consumer want to get only current value
//and not interested in updates
return mMovieCache.getValue();
}
public void clearCache() {
//CAUTION consumer should be ready to receive null value
mMovieCache.onNext(null);
//another way is to call mMovieCache.onCompleted();
//in this case consumer should be ready to resubcribe
}
public static class MovieWrapper {}
}
Take a look at BehaviorSubject marble diagram.

Testing okHttp requests with Robolectric - callbacks

I have a function that I want to test which runs in an okHttp callback. I'm trying to test it using Robolectrics but the callback is never executed. I presume that is because the test moves on after request without waiting for okHttp to return. So far I've tried:
ShadowLooper.pauseMainLooper();
Robolectric.flushBackgroundScheduler();
ShadowLooper.unPauseMainLooper();
but that didn't work. Any suggestions?
EDIT:
Here's an example of my code:
ApiClient.sendSomeDataToServer(data, callback);
Where ApiClient is a helper class containing okHttp client. sendSomeDataToServer API call looks something like this:
public static void sendSomeDataToServer(MyObject data, Callback callback){
final Request request = new Request.Builder()
.url(API_SOME_URL)
.post(RequestBody.create(JSON, myObject.getAsJson().toString()))
.build();
sHttpClient.newCall(request).enqueue(callback);
}
Where sHttpClient is an initialised OkHttpClient.
I can test the execution of above by forcing Thread.sleep(5000) inside my test code and providing custom callback. The code I'm trying to test is inside the callback. Any suggestions how I can test that? I really don't want to change the main code to fit the test framework - should be the other way round.
Lets assume you have next code. Interface:
#GET("/user/{id}/photo")
void listUsers(#Path("id") int id, Callback<Photo> cb);
Implementation:
public void fetchData() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setServer("baseURL")
.build();
ClientInterface service = restAdapter.create(ClientInterface.class);
Callback<Photo> callback = new Callback<Photo>() {
#Override
public void success(Photo o, Response response) {
}
#Override
public void failure(RetrofitError retrofitError) {
}
};
service.listUsers(435, callback);
}
First of all you need to change service instantiation to service injection (as parameter or field). I will do it as parameter:
public void fetchData(ClientInterface clients) {
}
After this text is quite trivial:
#Test
public void checkThatServiceSuccessIsProcessed() {
ClientInterface mockedClients = mock(ClientInterface.class);
activity.fetchData(mockedClients);
// get callback
ArgumentCaptor<Callback<Photo>> captor = (ArgumentCaptor<Callback<Photo>>)ArgumentCaptor.forClass(Callback.class);
verify(mockedInterface).listUsers(anything(), captor.capture());
Callback<Photo> passedCallback = captor.value();
// run callback
callback.success(...);
// check your conditions
}
The used library for mocking and verifying is Mockito.
There will be one warning with captor instantiation because of generics but it fixable if you will use #Captor annotation instead of creating captor by hands.
The parameter injection is not perfect, especially for case of activities. This was used to simplify example. Consider for proper injection with library or without. I would encourage you to try Dagger for injections
You can use ArgumentCaptor (Mockito's class).
Reference:
http://www.mdswanson.com/blog/2013/12/16/reliable-android-http-testing-with-retrofit-and-mockito.html

Mapping error responses with 200 response code in Retrofit

I'm dealing with an API that I don't control which returns error responses in JSON format, but doesn't return a non-200 response code in that case. Is it still possible to get anything that is an error (as determined by the presence of an "error" property) in the failure callback when using Retrofit? It's safe to assume I can recognise error responses from that API by looking at the message contents.
This is an application-level distinction which means Retrofit doesn't (and shouldn't) care about it.
There's three ways to accomplish what you want, each of which maps to a behavior of Retrofit.
For asynchronous invocation you can use a custom Callback subtype which does the mapping.
public abstract class MyCallback<T extends MyResponse> implements Callback<T> {
#Override public final void success(T data, Response response) {
if (!data.success) {
success(data);
} else {
error(data.error, response);
}
// This is just an example of what a potential common handler could look like.
}
public abstract void success(T data);
public abstract void httpError(Error error, Response response);
}
If you are using the experimental RxJava support you should map the Observable through something which extracts the error.
service.doSomething()
.map(new Func1<SomethingResponse, SomethingResponse>() {
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(..);
Finally, for synchronous you can obviously check at every call site, but you can also wrap the API.
public final class WrappedService implements Service {
private final Service real;
#Override public SomethingResponse doSomething() {
return handleError(real.doSomething());
}
private static <T extends MyResponse> T handleError(T data) {
if (!data.success) {
throw new SomeException(data.error);
}
return data;
}
}
(Hint: You can also use a Proxy to do this for every method automatically!)
The important thing to remember here is that Retrofit's responsibility is to map your HTTP API to a Java API. It is not responsible for applying application-level behavior or constraints to the request or response data. That remains your responsibility.
Looks this is possible in the Converter, from the javadoc:
ConversionException - if conversion was unable to complete. This will trigger a call to
Callback.failure(retrofit.RetrofitError) or throw a RetrofitError. The exception message
should report all necessary information about its cause as the response body will be set to
null.

Categories

Resources