RxJava — FlatMap vs. SwitchMap
Before we start
If you are an Android developer, there is high chance you’ve heard of RxJava or already published apps that uses it. It makes concurrency look easy, it simplifies Android application development.
If you don’t know about it yet, I highly recommend you to check out Realm Academy posts. This article is for someone who is already familiar with RxJava and its basics.
In this story
We will compare FlatMap and SwitchMap transformations, look at the diagrams and see how they are used in real application example. Lastly, I will show you how to write unit tests for them.
If you want to play with the full source code of the examples used here, check out this Github repository.
Alright, grab some ☕️ and let’s get started!
FlatMap vs. SwitchMap
FlatMap is popular transformation operation widely used by developers using Rx. Put it simply, it will turn items emitted by an observable into observables themselves, then merge emissions of those observables into one observable that emits all the items in no particular order. Resulting stream of events will contain items corresponding to all the emissions from the original observable and some of them may interleave. See the diagram below.
For some use cases it is perfectly fine, but there are situations where you may want more efficient flow and preserve the order of emitted items.
SwitchMap is another Rx transformation operation that is different to FlatMap in that it will unsubscribe from previous observable after emitting new one ensuring that only items from most recently emitting observable are emitted. Following diagram should explain this.
If you want to see more detailed diagram, check this one.
Real example
Looking at the official documentation it is kind of clear what is the difference between the two. What is not very clear though, is the real life use case when one is preferred over another.
Here I tried to come up with good example demonstrating how SwitchMap solves the problem that the FlatMap can not.
The example is simple application consisting of an ImageView
and several buttons. Each button has a fruit name on it. Clicking any of the buttons will fetch an image with corresponding fruit from an image repository and update the ImageView
. For the sake of example, image repository is configured to return an apple image within three seconds and others will be fetched instantly.
Using FlatMap
First, let’s look at the implementation with FlatMap. The view model will listen to click events and use FlatMap to map the clicked button ids to the images from the image repository:
The animation below demonstrates what happens when all three buttons are clicked in a sequence.
When user clicks Apple, Banana, and Orange buttons sequentially the Orange image should be the one that stays rendered. As you can see, this is not the case and the Apple appears sometime later.
What’s happening? The problem here is that FlatMap doesn’t cancel previous observable when new item is received and turned to an observable. So the request for the Apple image is not cancelled when user clicks the Banana button. By the nature of FlatMap, every click event will produce an image and the view model will render it. Since the Apple image takes longer to fetch, it is emitted the last and overrides the Orange image.
Using SwitchMap 🎉
To solve the issue with the Apple image we replace FlatMap with the SwitchMap.
As it was mentioned in the beginning of the article, SwitchMap is called “switch” because it acts like one: it will unsubscribe from previous observable every time it gets a new item that needs to be mapped, completely switching from one item to another. In our example it will guarantee, that the request for the Apple image is cancelled prior to handling the Banana image.
As you can see, the Apple image is not displayed anymore. Following digram shows the events timeline.
Once Banana button is clicked, the Apple observable (requesting an apple image) is canceled immediately.
I deliberately made my example such that it is obvious when FlatMap produces undesirable side effect. But it is not always evident from the behaviour if the code runs as intended. Imagine the code that wrongfully uses FlatMap for network requests or asynchronous CPU intensive computations. This will at best waste precious user’s resources like battery and network, and at worst may lead to random bugs that are hard to reproduce.
Conclusion
If you don’t care about order of emitted items and absolutely need all of them to be emitted — use FlatMap.
If you are in the situation when every subsequent event is somewhat a replacement for the previous one — use SwitchMap.
Oh wait, how about Unit Testing?
We don’t need to write unit tests for RxJava operators themself(They are maintained by smart engineers and have unit tests).
But, we may want to write some unit tests for view model that uses them to guarantee that it will emit correct items. It will save you from trouble later, when new developer will naively change the operator in the method.
See the unit tests in this file on git repository, it emits only Second_Bitmap.
There is another unit test for FlatMap that emits both First_Bitmap
and Second_Bitmap
Food for thoughts
Could we use ConcatMap
? What’s going to happen if we use ConcatMap
?
How about ConcatMapEager
? Check operators documentation site and find out :)