Making RxJava code tidier with doOnSubscribe and doFinally

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 two handy operators that I find make this type of code that little bit cleaner: doOnSubscribe and 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.
  • 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.

Applying these to the example above results in the following:

public void onViewCreated(View view) {

loadFromServer()
.compose(applySchedulers())
.doOnSubscribe(__ -> view.showLoadingIndicator())
.doFinally(() -> 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.

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