Making RxJava code tidier with doOnSubscribe, doFinally and doOnTerminate

I’m a big RxJava enthusiast. As an Android developer, I use it because it helps streamline my development by cutting down on a lot of the boilerplate that’s generally associated with asynchronous tasks.

As I got more comfortable with Rx, though, I noticed that a lot of my use cases for RxJava follow the same pattern in their setup and finish logic. A common example — loading data for a screen in an app. The code in almost all cases goes like this:

  1. Show loading state
  2. Load data
  3. When data has loaded hide loading state and update UI.
  4. Or if there was an error, hide loading state and show some error message.

And here is what that might look like in code.

public void onViewCreated(View view) {

view.showLoadingIndicator();

loadFromServer()
.compose(applySchedulers())
.subscribe(data -> {
view.hideLoadingIndicator();

view.showData(data);
}, error -> {
view.hideLoadingIndicator();

view.showError(error);
});

loadData();
}

You probably already spotted it — that annoying small bit of duplication, view.hideLoadingIndicator(). You may say it’s hardly worth mentioning but this is because it’s just one line we’re duplicating in this case. You also often come across more complex logic which has to happen regardless of what state the Rx-chain finishes in. There are two other reasons why I don’t prefer this:

  • view.showLoadingIndicator() is disconnected from the chain despite being logically tied to it and
  • The error handling logic contains code that is not error-handling specific.

Here is where I want to introduce three handy operators that I find make this type of code that little bit cleaner: doOnSubscribe , doOnTerminate doFinally (I should point out that in here I’m talking about RxJava 2, not the 1.x version) Although their names are quite descriptive, let’s look at what they do.

  • doOnSubscribe — Modifies the source so that it invokes the given action when it is subscribed from its subscribers.
  • doOnTerminateCalls the specified action just before this Observable signals onError or onCompleted.
  • doAfterTerminate — Calls the specified action just after this Observable signals onError or onCompleted.
  • doFinally — Calls the specified action after this Observable signals onError or onCompleted or gets disposed by the downstream.

The first one allows us to execute an action as soon as the Observable is subscribed to (e.g. starting off a network call) and the second one allows us to execute an action when the call finishes regardless of whether it was successful or not. doAfterTerminate is similar but it gets called after your subscribe function. The fourth is the same as doAfterTerminate but it also calls the action after the observable is disposed, not just completes or errors out. Depending on your use case, you might prefer to use one or the other for finalisation use cases.

Applying these to the example above results in the following:

public void onViewCreated(View view) {

loadFromServer()
.compose(applySchedulers())
.doOnSubscribe(__ -> view.showLoadingIndicator())
.doOnTerminate(() -> view.hideLoadingIndicator())
.subscribe(data -> {
view.showData(data);
}, error -> {
view.showError(error);
});

loadData();
}

Small change, but it has tidied up the issues we had with the previous style:

  • The setup code is now executed as part of the chain and
  • The subscribe handlers deal only with what directly concerns them.

We’ve also added a small bonus — the setup and finish logic are declared right next to each other so it’s made the code a little bit easier to scan as well.

You might be wondering why I used doOnTerminate instead of doFinally here. In this case I dispose of my observable after my screen is destroyed so I rather not call UI operations in that case (there’s no point).

I hope by reading this you’ve come to appreciate the benefits that small changes like these can bring. Remember — small improvements matter.