RxJava: FlatMap, SwitchMap and ConcatMap differences & examples
Transforming items emitted by an Observable into other Observables is vital part of programming in RX. There are three common operators to do it and each one has different advantages over another. In this story I will try to explain subtle differences and point the best usages of those operators.
Lets start with a simple Java unit test that is testing flatMap operator. First we create list of Strings, then transform each object of this list into an Observable using operator from. Then each item is flapMapped into an observable that adds “x” letter at the end of the string. Next the observable is delayed by na random number of seconds (line 9). Last thing is advancing in time by 1 minute (line 17), just to be sure that everything will have the time to emit. (If we would not do this, the test would end before any emission).
Lets run this test to see its result: [cx, ex, fx, bx, dx, ax]
Surprised? You shouldn’t be because as documentation points out:
Note that FlatMap merges the emissions of these Observables, so that they may interleave.
So what actually happened here is operator flatMap does not care about the order of the items. It creates a new observable for each item and that observable lives its own life. Some of them will emit faster, and others slower because we delay each of them for a random amount of seconds. Similar effect will happen without delaying, but it was added for the sake of an argument.
The same test, the same conditions, but with operator switchMap instead of flatMap:
Documentation here is pretty self explanatory:
whenever a new item is emitted by the source Observable, it will unsubscribe to and stop mirroring the Observable that was generated from the previously-emitted item, and begin only mirroring the current one.
The same test, now with concatMap operator:
And the result: [ax, bx, cx, dx, ex, fx].
ConcatMap works almost the same as flatMap, but preserves the order of items. But concatMap has one big flaw: it waits for each observable to finish all the work until next one is processed. Lets see how it works in example:
There are two schedulers to make the difference clear between those two operators. And the output is:
5000 5000 5000 5000 5000 5000
5000 10000 15000 20000 25000 30000
This simple test proves that although concatMap is very convenient to use, it must be used with caution because it will break asynchrony and make the whole process take longer.
Lets see how this works in the real life. Note that those examples are not universal, usages may vary depending on your implementation.
- Handling new data (eg. list of posts in the feed).
Lets assume somewhere in the application there is an Observable that periodically emits list of objects. It can be list of the posts in the timeline that is refreshed each time user makes interaction with it. In this case the best operator to use would be switchMap because we don’t care about the previous result if we have a new set of items. It is safe to unsubscribe from it and focus on the newest data. This can save us some time if processing of previous (old) response is skipped.
- Getting specific data for each item from a list (eg. get avatar for each user in contacts).
In this case I would recommend using concatMap. When using flatMap here, there is a possibility of messing up the order of the avatars, and wrong avatar can be displayed. It depends of your implementation though, it may be still ok to use flatMap if you haver other mechanism to preserve the order. SwitchMap is not a good idea here, because we will not get all avatars as described above.
- Doing something for each item in sorted list:
FlatMap or switchMap should not be used in this case. Only concatMap will make sure that our list stays the same. Because of synchronous calls in the concatMap, increase of the processing time must be taken into account.
- Sending some information for each item in list (eg. sending ‘like’ message for each post in the list).
To make sure that every request will be called, we definitely should not use switchMap here. Both flatMap and concatMap will do the job, but flatMap would be better as we don’t care about the order and we can call all requests altogether and receive the results faster.
- Searching through items by query.
Lest assume that user inputs letters: ‘x’ then ‘y’. The whole query is now ‘xy’ so there is no need to be subscribed for results with the letter ‘x’. In this case we can safely use switchMap.
If you don’t want to wonder what transforming operator you should use and you don’t care about optimising your code, concatMap would almost always do the job well. But be careful when using it on heavy operations.
It is better to know the differences between those three operators and take advantage of their strengths.
You almost never want to use switchMap after Observable.from(). And if you have to do it, do it with caution.
flatMap: not preserving the order of items, works asynchronously
switchMap: unsubscribing from previous observable after emitting new one
concatMap: preserving the order of items, works synchronously