Avoid Repeated Expensive Computations with RxJava

Shaishav Gandhi
AndroidPub
Published in
6 min readAug 5, 2018

RxJava is a godsend when navigating asynchronous behaviors and patterns in Android apps. Today, we’re gonna take a look at how to ideally use RxJava to avoid repeating expensive operations.

Let’s take an example of a simple application which performs the expensive operation of loading a Bitmap from a raw image file. The UI of the app looks something like this:

When the user taps on the load image button, we’ll load an expensive Bitmap onto an ImageView above the Button.

The flow will be something like:

  • The user taps the button
  • We make a request to load the Bitmap via an Observable.
  • We get the Bitmap when the image is loaded and we set it to the ImageView.

We’re going to use the Android Architecture Components’ ViewModel as our presentation layer and we’ll use an ImageRepository to actually load the Bitmap from a raw resource and expose it as an Observable (Note, you can also use LiveData for this but for the purposes of this blog post, we’ll be using Rx). This is a pattern that is recommended by Google which provides good separation of concerns between presentation, data and view layers.

Our ImageRepository looks something like this:

The code here is straightforward. We have a method loadImage which takes a raw resource as a parameter and returns an Observable<Bitmap>. An easy Rx operator that can be used to defer expensive computation is fromCallable. We simply use the BitmapFactory to decode a raw resource to a Bitmap.

This is what our ViewModel looks like:

We have a custom Factory class which helps us in passing ImageRepository as a constructor parameter.

Finally, our Activity:

Here, we simply initialize our ViewModel and when the user clicks on the Button, we make a call to load the image.

Since we’re using RxJava, we will want to handle disposing of our subscriptions to avoid memory leaks. We do this using Uber’s AutoDispose library, which handles the subscriptions for us. (Fun Fact: This whole post is based around a sample that I contributed to AutoDispose to demonstrate how AutoDispose would work in real world applications).

When we run the application and click on the “Load Image” button, the image loads! It looks something like:

That’s awesome! We loaded the raw image successfully! BUT WAIT! I accidentally rotated my phone and now this is what my app looks like:

Yup! We lost the state of our application, which is a pretty bad experience for the user. The user would now be completely confused as to what they were doing and where their precious image went. This happened because we didn’t account for screen rotation and other configuration changes.

Problems

When we analyze the flow we took, we can easily see that it is flawed. There are a few problems:

  1. We aren’t utilizing Rx’s Observable stream properly. The loadImage function in ImageRepository returns a cold observable. This means that after the Observable emits one item, it completes (calls onComplete) and basically dies. We aren’t caching any results either so once it emits something, it’s lost. This means that if another screen calls the same loadImage, it will again fetch the image from raw resource and do the expensive computations. For a detailed explanation between hot and cold observables, refer this excellent resource.
  2. We only subscribe to the stream when the user clicks on the button. This ties together well into how we’ve structured the application. We’re basically using RxJava stream to do one-time work, instead of taking advantage of its streaming and caching abilities.

Let’s go about fixing these problems. As a reference, here’s the application code for the functionality we’ve done so far.

Caching Results

What we want to achieve is that we cache the Bitmap that was last emitted by our Observable and reuse that unless we explicitly ask for a fresh reload.

Let’s first take a look at what changes we’ll need to make to the ImageRepository.

Create a Hot Observable

Simply put, a hot Observable is one which follows it’s own timeline, independent of it’s subscribers. For example, think about mouse movement events. These events will keep emitting, even if no one is listening to them. Once someone starts listening to them, they’ll get the subsequent events.

To transform the loading of an image to a hot Observable, we’ll use Subjects. A Subject is both an Observable as well as a Subscriber. It’s a common paradigm to use a Subject to bridge the gap between non-Rx code and Rx code. There are different kinds of subjects and you can find out more about them here, but for this example we’ll use a BehaviorSubject. A BehaviorSubject will emit all items that are emitted after subscribing to it as well as the last emitted item before the subscription.

In our ImageRepository we will expose a BehaviorSubject, that will accept an Integer (which represents our raw resource id). We will then map this Integer to load a Bitmap and return this transformed Observable.

Our updated ImageRepository looks something like:

Notice how this time, we’ve separated the act of asking for a new image and observing for the result. This makes sense because we might not want to couple the act of loading images and receiving images into the same method.

ViewModel:

Activity:

As you may have noted, we’ve changed our structure this time. We observe for the results when the Activity starts. When the user clicks on the Button, we simply ask for the results. By decoupling observing the results and the click listener, we’re able to recover the results.

Once you run the application, the image still loads after rotation! However, if you notice, we logged a statement in ImageRepository when the load from the raw resource happens. Once you rotate the phone, you’ll see that it’s logged again.

2018-08-05 13:41:25.630 12766-12766/com.shaishavgandhi.rxreplayingsharesample D/ImageRepository: Performing expensive operation
2018-08-05 13:41:28.957 12766-12801/com.shaishavgandhi.rxreplayingsharesample D/ImageRepository: Performing expensive operation

This means, we’re getting the image back but we’re actually loading it twice by performing the operation of loading it from raw resource twice.

This happened because when we rotated the phone and resubscribed, the imageId in the BehaviorSubject was emitted again (since its the property of BehaviorSubject’s to emit the last emitted value) and we performed the same operation of loading the resource again.

One Last Step

To avoid the expensive operation we will use a library called RxReplayingShare by Jake Wharton to cache the actual results of the Bitmap instead of the imageId. From the library’s README:

ReplayingShare is an RxJava 2 transformer which combines replay(1), publish(), and refCount() operators.

ReplayingShare is an RxJava 2 transformer which combines replay(1), publish(), and refCount() operators.Unlike traditional combinations of these operators, ReplayingShare caches the last emitted value from the upstream observable or flowable only when one or more downstream subscribers are connected. This allows expensive upstream sources to be shut down when no one is listening while also replaying the last value seen by any subscriber to new ones.

By simply appending replayingShare() to your upstream observable (in our case, our Subject), RxReplayingShare will cache the results and emit them when we subscribe back.

This time, we use a PublishSubject instead of a BehaviorSubject. The reason being that we don’t really want to cache the last emitted resourceId, but instead we want to cache the last emitted Bitmap.

Our updated ImageRepository now looks like:

Now if we run our app, the image loads and if you rotate, we get the cached Bitmap value. Once we check our logs, we’ll see that on rotation, there was no logging! Wohoo!

The same concept can be applied to network calls or any other expensive operations.

The code for the sample is hosted on GitHub. You can find it here. You can also find the intermediate step here.

Finally, you can find the entire AutoDispose sample here.

--

--