RxJava 2: Android MVVM Lifecycle App Structure with Retrofit 2

Manuel Vivo
Nov 12, 2016 · 12 min read

It’s been more than two years since I wrote this post about MVVM, RxJava and Retrofit. It seems a bit old-fashioned to me right now. It’s surprising how much you can learn in one year! You take a look back and you might be embarrassed by your own code. Not only the code itself but also the process of how you get there. All that seems legacy code to me.

In this article, I’m going to try to improve my first article with new scenarios and libraries. Let’s continue with the same example (catch up here for more info). This time, I’m going to use RxJava 2 in a real example within a MVVM structure using Retrofit. We’re also going to talk about how you can improve the performance in your app with Network Requests responding to the View’s lifecycle.

Catching Up

App structure

If we go through the different layers quickly…

  • Retrofit Layer: it actually makes the network request.
  • APIService Layer: it’s in charge of making the network request, parsing the response and processing it if needed.
  • RequestManager Layer: prepares the data to be sent and links different network requests.
  • ViewModel Layer: handles the logic that its View requires.
  • View Layer: Views are dummy. Just handles user input.

Hands On

During the article, I’m going to mention and talk a lot about a pet project in which you can see how everything is implemented.

Lifecycle Causing Issues Between Views and ViewModels?

In the previous article using RxJava 1, we had Subjects in the ViewModels that replied the information to the Views that used Subscribers. You remember the part when I said I learned a lot in two years? Well, this is one example.

We all have had the same problem: I don’t want to cancel the network request or make multiple ones if the app goes to the background.

One of the problems we’re facing here is that the Subscriber/Observer onNext() or onComplete() method gets called and the View is not on screen. If the Subscriber tries to communicate back to the View (either with a BusManager or a Callback), and within that method we try to update any UI widget, the app might crash. The Subject was quite helpful when holding the information, until the View appeared to get it.

If you take a look at the new repo, the communication between Views and ViewModels is by an interface (callback) called Contract. That gives you the flexibility to plug and play any View on top of the ViewModel. Imagine that you have different Views depending on if it’s a smartphone, tablet, smartwatch… All of them could potentially share the same ViewModel, but not necessarily the other way around.

How to Solve the Lifecycle Problem?

An interface is defined to communicate what’s happening in each moment.

public interface Lifecycle {

interface View {

}

interface ViewModel {

void onViewResumed();
void onViewAttached(@NonNull Lifecycle.View viewCallback);
void onViewDetached();
}
}

The View is going to call Lifecycle.ViewModel#onViewResumed() in its onResume() method. Lifecycle.ViewModel#onViewAttached(this) in the onStart() one and Lifecycle.ViewModel#onViewDetached() in onDestroy().

With that, the ViewModel is aware of the Lifecycle and the logic to decide when to show something or not goes to the ViewModel (as it should be) so it can act accordingly and notify the view when it gets the information.

Contract Between View and ViewModel

A contract defines what the View needs from the ViewModel and vice versa. Usually you define a contract by screen, although you can define it per functionality as well.

In our example, we have the Home screen which has the ability to refresh the User data. We could define our contract as:

public interface HomeContract {

interface View extends Lifecycle.View {

void showSuccessfulMessage(String message);
}

interface ViewModel extends Lifecycle.ViewModel {

void getUserData();
}
}

This contract extends from the Lifecycle one, so the ViewModel is going to be lifecycle-aware as well.

RxJava 2 Reactive Stream types

With RxJava 2, some concepts have been introduced and some others have been renamed. Take a look at the documentation for more information.

The main difference between them is the back-pressure handling. Basically, in RxJava2 a Flowable is an Observer that handles back-pressure. The same relationship links a FlowableProcessor to a Subject, a Subscriber to an Observer, etc.

Remember that a Completable, Single and Maybe don’t handle back-pressure.

For learning purposes, we are going to make Retrofit return an Observable object. What if we want to handle back-pressure? And what if we know the result we expect and want to optimize our code to specify the Stream we want to get? We’ll get into that.

Using Completable

Let’s take as an example the Registration call. Because the RegistrationAPIService is processing the information, we don’t want to get a Stream back since the response is not used by the RequestManager layer. We just care about the call being successful. For that, we return a Completable object ignoring the elements that we get from the Observable.

public Completable register(RegistrationRequest request) {

return registrationAPI.register(request)
.doOnSubscribe(disposable -> isRequestingRegistration = true)
.doOnTerminate(() -> isRequestingRegistration = false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(this::handleRegistrationError)
.doOnNext(registrationResponse -> processRegistrationResponse(request, registrationResponse))
.ignoreElements();
}

Using Maybe

If we want to pass the response back to the RequestManager layer, but because it’s a network request we know that we’re only going to receive one object, we can use Maybe (it might be that the body is empty, so we use Maybe to avoid exceptions when null objects).

Remember to use the singleElement() operator and not the firstElement() operator. If you use the second one and you get nothing, it will throw an exception since it always tries to access the first element, even if it’s not there.

public Maybe<LoginResponse> login(LoginRequest request) {

return loginAPI.login(request.getNickname(), request.getPassword())
.doOnSubscribe(disposable -> isRequestingLogin = true)
.doOnTerminate(() -> isRequestingLogin = false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(this::handleLoginError)
.doOnNext(this::processLoginResponse)
.singleElement();
}

Using Flowable

As we said before, a Flowable will have the same behaviour as an Observable, but will handle back pressure. For this, when converting an Observable to a Flowable, we have to specify which strategy we want to use.

These are different strategies:

  • BUFFER: Buffers all onNext values until the downstream consumes it.
  • DROP: Drops the most recent onNext value if the downstream can’t keep up.
  • ERROR: Signals a MissingBackpressureException in case the downstream can’t keep up. (This is the same behaviour as an Observable.)
  • LATEST: Keeps only the latest onNext value, overwriting any previous value if the downstream can’t keep up.
  • MISSING: onNext events are written without any buffering or dropping.

In our Games example, we’ll use the BUFFER strategy since we don’t want to lose any game in case the downstream can’t keep up. It might be slower, but all of them will be there.

public Flowable<GamesResponse> getGames(GamesRequest request) {

return gamesAPI.getGamesInformation(request.getNickname())
.doOnSubscribe(disposable -> isRequestingGames = true)
.doOnTerminate(() -> isRequestingGames = false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this::handleAccountError)
.toFlowable(BackpressureStrategy.BUFFER);
}

Using Zip Operator to Make Different Network Requests at the Same Time

If you want to make different network requests at the same time and only be notified when all of them have succeeded, you should use the Zip operator. It’s really powerful and it’s one of my favorites!

#UserDataRequestManager.javapublic Flowable<Object> getUserData() {

return Flowable.zip(
getAccount(),
getGames(),
this::processUserDataResult);
}
private Flowable<AccountResponse> getAccount() {

return accountAPIService.getAccount(createAccountRequest());
}

private Flowable<GamesResponse> getGames() {

return gamesAPIService.getGames(createGamesRequest());
}

Concatenating Different Network Requests

We can see how each network request returns a different type of Stream. Let’s see how we can chain them. The idea is to make the Registration request, Login, and then the UserData all in one go.

UserData returns a Flowable, however, the Login request returns a Maybe so we have to match them:

#AuthenticationRequestManager.javaprivate MaybeSource<Object> makeGetUserDataRequest(LoginResponse loginResponse) {

return userDataRequestManager.getUserData().singleElement();
}

The Login request is going to get the UserData if the response is successful. As we prepared the getUserDataRequestMethod to return a Maybe, we can concatenate them with a flatMap() operator:

#AuthenticationRequestManager.javapublic MaybeSource<Object> login() {

return loginAPIService.login(createLoginRequest())
.flatMap(this::makeGetUserDataRequest);
}

Now, if we want to make the registration call and then the Login request, we just have to call it after the Completable finishes. We do that with the andThen() operator.

#AuthenticationRequestManager.javapublic MaybeSource<Object> register() {

return registrationAPIService.register(createBodyForRegistration())
.andThen(makeLoginRequest());
}
private MaybeSource<Object> makeLoginRequest() {

return login();
}

Observing Sources

If we take a look at the documentation, we can see how Observers (for Observables) and Subscribers (for Flowables) expose a new method in their interface: onSubscribe().

An Observer subscribes with a Disposable, which lets it dispose/cancel the subscription. A Subscriber subscribes with a Subscription, which apart from cancelling it, it can request items (we can see the back-pressure functionality here).

Most of the time, we don’t want to override onSubscribe method in an Observer or Subscriber (as we used to do with RxJava 1). For that, we just have to subscribe on the Stream with a DisposableObserver or a DisposableSubscriber.

When you’re observing a Stream, if you want to get the Subscription/Disposable you have to use the method subscribeWith()instead of subscribe().

If you’re not going to unsubscribe, you can use subscribe():

public void getUserData() {

userDataRequestManager.getUserData()
.subscribe(new HomeSubscriber());
}

If you want to unsubscribe/dispose:

public void getUserData() {

Disposable userDataSubscription = userDataRequestManager.getUserData()
.subscribeWith(new HomeSubscriber());

userDataSubscription.dispose();
}

Background Processing and Lifecycle

In order to avoid notifying the View when it’s not on the screen, we’d like to hold the information until the View becomes visible (ready to do what was supposed to do) and then dispatch the information. In our use case, we just want to make one network request instead of multiple reqeusts when the app is in the background or the View is not there.

Option 1: Using the Lifecycle Contract Methods

Using the Lifecycle methods, we’ve created an abstract class which is going to handle the request state for us. We can save the state and the last error that happened there.

We also have to create different Observers depending on the type of Stream. For example, the Login request is handled by a MaybeObserver.

protected class MaybeNetworkObserver<T> extends DisposableMaybeObserver<T> {

@Override
public void onSuccess(T value) {

requestState = REQUEST_SUCCEEDED;
}

@Override
public void onError(Throwable e) {

lastError = e;
requestState = REQUEST_FAILED;
}

@Override
public void onComplete() {

}
}

As you can see, onSuccess(T) is the method which set the requestState to SUCCEEDED in this case, because it’s a DisposableMaybeObserver (if it was a DisposableObserver then that’d be in the onComplete() method). This Observer is going to be used by the Login ViewModel when making the network request. If we take a look at that class, the methods are defined as:

public class LoginViewModel extends NetworkViewModel implements LoginContract.ViewModel {   public void login() {

authenticationRequestManager.login()
.subscribe(new LoginObserver());
}
}private class LoginObserver extends MaybeNetworkObserver<Object> {

@Override
public void onSuccess(Object value) {

onLoginCompleted();
}

@Override
public void onError(Throwable e) {

onLoginError(e);
}

@Override
public void onComplete() {

}
}

onLoginError() and onLoginCompleted() are methods defined inside the class that handle the happy and sad scenarios. As you can see, in this case we can call subscribe() on the authenticationRequestManager Maybe Stream since we’re not going to unsubscribe from it.

How do we handle this when the app goes to the background? We use the onViewResumed() method:

@Override
public void onViewResumed() {

@RequestState int requestState = getRequestState();
if (requestState == REQUEST_SUCCEEDED) {
onLoginCompleted();
} else if (requestState == REQUEST_FAILED) {
onLoginError(getLastError());
}
}

When the view is resumed, if our state is REQUEST_SUCCEEDED then we notify the View. If it failed, we notify with the error. As you might have noticed, the code inside the LoginObserver class has been called when the response came. We would’ve notified the View if it was there, right? We need a null check to avoid calling the View when it’s not there. See code below:

private void onLoginCompleted() {

if (viewCallback != null) {

viewCallback.hideProgressDialog();
viewCallback.launchHomeActivity();
}
}

Option 2: Using a Processor (a Subject With Back-Pressure Support)

When we pull to refresh on the HomeActivity, the HomeViewModel is going to get the UserData. Instead of using a normal Subscriber, we’re going to use a Processor.

This solution is designed with a Pull To Refresh behavior in mind. We always want to make that network request; if you don’t want to make multiple network requests and just get the last response, the implementation would be slightly different.

For this example, we’re going to use an AsyncProcessor because we just want the last information emitted by the Source that hasn’t been consumed yet, not all the elements.

When we pull to refresh, we always make the getUserData() network request. However, when the view is detached from the ViewModel, we don’t cancel the network request and we process the information when the view is resumed.

The key is the AsyncProcessor. This object is the one which is going to subscribe to the UserData Flowable, and it’s going to hold the information until a Subscriber asks for it.

Because we always want to make the network request, we create a new AsyncProcessor every time. Then, we subscribe to the AsyncProcessor with the object we want to get the response and hold it in a local field (so we can dispose it when the view is detached). Finally, we make the network request using the AsyncProcessor as Subscriber.

# HomeViewModel.java
private AsyncProcessor<Object> userDataProcessor;
private Disposable userDataDisposable;
public void getUserData() {

userDataProcessor = AsyncProcessor.create();
userDataDisposable = userDataProcessor.subscribeWith(new HomeSubscriber());

userDataRequestManager.getUserData().subscribe(userDataProcessor);
}

What happens when the View is detached? We cancel our current Disposable. Notice that the network request is not cancelled since it’s been subscribed with the AsyncProcessor.

@Override
public void onViewDetached() {

this.viewCallback = null;

if (isNetworkRequestMade()) {
userDataDisposable.dispose();
}
}
private boolean isNetworkRequestMade() {

return userDataDisposable != null;
}

When the View is resumed, we check if we had made a network request. If so, we reconnect our Subscriber to the AsyncProcessor. If the network request is on its way, we’re going to get notified when it comes. If it had already come, we are notified straightaway.

@Override
public void onViewResumed() {

if (isNetworkRequestMade()) {

userDataProcessor.subscribe(new HomeSubscriber());
}
}

The characteristic of this solution is that the Subscriber’s code is never executed in the background. Because of that, we don’t have to check for view nullability because the viewCallback object is never going to be null.

private class HomeSubscriber extends DisposableSubscriber<Object> {

@Override
public void onNext(Object o) {

}

@Override
public void onError(Throwable t) {

viewCallback.showSuccessfulMessage("Refreshed");
}

@Override
public void onComplete() {

viewCallback.showSuccessfulMessage("Refreshed");
viewCallback.hideLoading();
}
}

Mocking the Retrofit Network Request

If you take a look at the project, I’m mocking the network requests with a Client and adding a delay so I can test it when the app goes to the background.

Use the RxJava2CallAdapterFactory on the Retrofit Builder to enable the RxJava 2 features on Retrofit.

public static Retrofit getAdapter() {

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new MockInterceptor())
.build();

return new Retrofit.Builder()
.baseUrl("http://www.mock.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}

The interceptor always returns a successful response after two seconds. It can be improved, checking which request has been made and then returning the right JSON response as part of the body.

public class MockInterceptor implements Interceptor {

@Override
public Response intercept(Chain chain) throws IOException {

addDelay();

return new Response.Builder()
.code(200)
.message("OK")
.request(chain.request())
.protocol(Protocol.HTTP_1_0)
.body(ResponseBody.create(MediaType.parse("application/json"), "{}"))
.build();
}

private void addDelay() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Other Considerations

If you take a look at the repo, some parts of the code are quite poor. Have you noticed the use of Singletons? (UserDataRequestManager for example) It hurts my eyes but I didn’t have time to make it better.

You might wonder… What’s the problem? Well, Unit testing singletons is terrible since they hold their state between unit tests.

How would you fix this? Dependency Injection! You can either do it manually, passing objects around (which it’s not ideal) or you can integrate Dagger 2 (which is much better than Dagger 1 because it’s all in compilation time). I’d avoid doing it manually since you end up with huge methods in the top-level architecture classes (mainly in the View) which creates and passes around objects that are only going to be used in the low-level parts of your architecture (**sigh**). Imagine a Fragment having to create an APIService and passing it through all the layers! Horrible!

Conclusion

Make sure when you migrate your code to RxJava2 that you use the Streams and Observers as you mean to.

This is a nice summary about how to structure your app using MVVM and handle the Views lifecycle in an effective manner.


Other Articles

What about making all this survive configuration changes? Check out this other article I wrote.

DISCLOSURE STATEMENT: These opinions are those of the author. Unless noted otherwise in this post, Capital One is not affiliated with, nor is it endorsed by, any of the companies mentioned. All trademarks and other intellectual property used or displayed are the ownership of their respective owners. This article is © 2017 Capital One.

Capital One Tech

The low down on our high tech from the engineering experts at Capital One. Learn about the solutions, ideas and stories driving our tech transformation.

Manuel Vivo

Written by

Android DevRel @ Google

Capital One Tech

The low down on our high tech from the engineering experts at Capital One. Learn about the solutions, ideas and stories driving our tech transformation.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade