.flatMap() is a map operation--it transforms one value to another. However, the way it does it is a bit different. Let's examine this with an example where we have a bunch of IDs, and we need to request some remote service to receive data:
Observable.just("ID1", "ID2", "ID3")
.map(id -> Observable.fromCallable(mockHttpRequest(id)))
.subscribe(e -> log(e.toString()));
Here, we have three IDs and each of them will initiate a new request using our mockHttpRequest method; that implementation detail is not important here, let's assume that it returns a working Callable interface instance. For the sake of argument, we can set it to something like this:
private Callable<Date> mockHttpRequest(String id) {
return Date::new;
}
Let's see what will be printed in the logs:
APP: io.reactivex.internal.operators.observable.ObservableFromCallable@24732724:main
APP: io.reactivex.internal.operators.observable.ObservableFromCallable@4ccd28d:main
APP: io.reactivex.internal.operators.observable.ObservableFromCallable@196acc42:main
It's not really something that we wanted to have--we got a bunch of Observables because it was the function of the .map() transform IDs to Observables. Let's take a look at the following line:
final Observable<Observable<Date>> map
= Observable.just("ID1", "ID2", "ID3")
.map(id -> Observable.fromCallable(mockHttpRequest(id)));
The Observable<Observable<Date>> type here is not something we want to work with--we are actually hoping for Observable<Date>.
Now there is very little we can do with them. The only way to get values from them is to do something like this:
Observable.just("ID1", "ID2", "ID3")
.map(id -> Observable.fromCallable(mockHttpRequest(id)))
.subscribe(e -> {
e.subscribe(value -> log("subscribe-subscribe",
value.toString()));
});
Now, it will print the values we've wanted to see:
APP: subscribe-subscribe:main:Sat Feb 30 12:12:12 GMT+01:00 2017
APP: subscribe-subscribe:main:Sat Feb 30 12:12:12 GMT+01:00 2017
APP: subscribe-subscribe:main:Sat Feb 30 12:12:12 GMT+01:00 2017
However, the way to do this is absolutely absurd--we need two levels of .subscribe()!
If we consider the previous conveyor comparison, the current setup will look like this:

We can see that here we have an Observable of Observables in which the actual items are carried. So, how do we fix that?
.flatMap() unwraps the unnecessary Observable so that the flow becomes like this:

It can be seen that now there is one level of Observables. So, if we replace the .map() call with .flatMap() so that it becomes this:
Observable.just("ID1", "ID2", "ID3")
.flatMap(id -> Observable.fromCallable(mockHttpRequest(id)))
We can just do a regular .subscribe() as we are used to:
Observable.just("ID1", "ID2", "ID3")
.flatMap(id -> Observable.fromCallable(mockHttpRequest(id)))
.subscribe(value -> log("subscribe-subscribe",
value.toString()));
In the end, the .flatMap() is a transformation that expects the result to be of an Observable type and unwraps the values of that new Observable into the original Observable that we are actually using so that it becomes much easier to consume.