
Progressive image loading with RxJava
To create an app with great user experience it is crucial to minimise the time the user waits for content to load. In most cases, it’s better to deliver image in worse quality before the image in original quality loads than to have him wait and stare at the blank screen.
In this post, I want to show how to implement progressive image loading in Android app using RxJava and Kotlin.
To load images I used one of the most popular images downloading library Picasso. But if you’re the Glide fan, keep reading, I prepared something for you at the end 😉
Fetching multiple images
If you ever used Picasso you surely know that to download an image and show it in the ImageView you simply call
Unfortunately, this method cannot be used when the images from two different URLs should be loaded into one view. Callingload(url).into(imageView
cause previous load into this view to be cancelled.
That’s why each image has to be loaded into the separate target where bitmap will be applied into ImageView when fetched. The sample code may look like this:
In the snippet I created anonymous Target class and when the bitmap is loaded I applied it into the ImageView.
But why I noted that it won’t work? If you run the code you will see that no image is ever loaded into ImageView. The reason can be found in theinto(target)
method JavaDoc.

To protect the targets from being garbage collected strong references have to be kept. One way to do it is to create field of type MutableList<Target>
and store targets in it until bitmap fetch completes.
Of course, it’s really important to remove targets once they’re no longer used to not waste resources.
But how to wire this together to achieve clean, readable solution? How to know when to apply bitmap and when to ignore it? That’s where RxJava comes in handy.
ViewModel
My idea was to pass url with list of qualities into method and receive event each time image with a better quality is received. Received bitmap will be stored in LiveData observed by the view.

The viewModel will post error if event streams complete and there is no image with sufficient quality.
The code of the ImageViewModel
is really simple and looks like this:
ImageFetcher
Now the fun part — the class responsible for creating observable for each image request and merging all of them together.
Two qualities
In the simplest scenario of fetching only two images simultaneously the only thing that needs to be done is to create observable for each url with quality and merging them together.
In the loadImageAndIgnoreError
method I created single from instance of the class implementing SingleOnSubscribe<BitmapWithQuality>
interface and then turned it into observable.
I pass Observable.empty<BitmapWithQuality>()
when the error occurs so the flow of other events is not disturbed by an error in one of the observables. The error will be displayed on the view only when fetched image has no sufficient quality after all calls complete.
Multiple qualities
But what about the situation when image in more than two qualities should be fetched? For this scenario I created a solution which uses combination of map, merge and reduce operators.
First I took the list of qualities and map them into Pair(url, quality)
. Then using map operator I created Observable from that pair using loadImageAndIgnoreError
, just like before. As the last step I used reduce operator to merge all observables together and fetch all images simultaneously.

Reduce operator fits great in this scenario. It applies a function to each item emitted by an Observable sequentially and emit the final value. In this case the final value is the observable created from merging all component observables together.

The resulting code of loadProgressively
method looks like this:
ImageFetcherSingleSubscribe
ImageFetcherSingleSubscribe
is a class implementing SingleOnSubscribe<T>
interface consisting of only one method: subscribe
. This method receives a SingleEmitter
instance that allows pushing an event in a cancellation-safe manner.
In the subscribe method I created CustomImageLoadTarget
which takes an emitter, fetched image quality and unSubscribe
function as a parameter. unSubscribe
function must be passed so target can notify ImageFetcherSingleSubscribe
that the request should be canceled and target removed from the list.
Next, I added the target into mutable list to prevent if from being garbage collected until call ends. Then the target is passed to the Picasso’s into
method.
removeTargetAndCancelRequest
method not only removes the target from the list but also cancels the request. The reason is that I call that function not only when image fetch completes but also when the emitter gets cancelled.
CustomImageLoadTarget
CustomImageLoadTarget
is a class implementing Target
interface which instance will be passed to into
method.
In the init method it’s really important to call emitter.setCancellable { unSubscribe(this) }
so the request is canceled and target is removed from the list after emitter was disposed.
Next part is really straightforward. When the bitmap is fetched in the onBitmapLoaded
method emitter.onSuccess()
is called with fetched bitmap and its quality. When fetching bitmap fails in onBitmapFailed
emitter.tryOnError
is called. After emitting either success or an error unSubscribe
must be called to remove target from the map and release reference so it may be garbage collected.
RxJavaErrorHandler
In one of my previous posts I wrote that it’s really important to use emitter.tryOnError
instead ofemitter.onError
to avoid getting io.reactivex.exceptions.UndeliverableException when error is emitted after observable was disposed. It’s still true and I applied that in my solution. However If you prefer, you can use emitter.onError
and add custom RxJavaErrorHandler
to ignore UndeliverableException
.
Glide thumbnails ❤️ 🚀
Above solution might after some changes be also applied if you use Glide. However, there is other, really simple way to implement progressive image loading in Glide: thumbnails.
Thumbnails are a dynamic placeholders, that can be loaded from the Internet. Fetched thumbnail will be displayed until the actual request is loaded and processed. If the thumbnail for some reason arrives after the original image, it will be dismissed.
For most cases this solution will be sufficient but if you want to get really crazy you can even apply additional thumbnail request to the thumbnail request.
The disadvantage of this solution is that you cannot apply very complicated error or progress handling logic. Solution that I implemented using RxJava gives you more control of the current state but is also way more complicated.
Full code of presented solution can be found on my repository on Github. To test it it’s convenient to reduce internet connection quality. You can do it using Charles — in throttle settings you can change internet speed. You can also install your app on an emulator and change internet connection speed in settings.