When do you use map vs flatMap in RxJava?

When do you use map vs flatMap in RxJava?

Say, for example, we want to map Files containing JSON into Strings that contain the JSON--

Using map, we have to deal with the Exception somehow. But how?:

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // So Exception. What to do ?
        }
        return null; // Not good :(
    }
});

Using flatMap, it's much more verbose, but we can forward the problem down the chain of Observables and handle the error if we choose somewhere else and even retry:

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override public void call(Subscriber<? super String> subscriber) {
                try {
                    String json = new Gson().toJson(new FileReader(file), Object.class);

                    subscriber.onNext(json);
                    subscriber.onCompleted();
                } catch (FileNotFoundException e) {
                    subscriber.onError(e);
                }
            }
        });
    }
});

I like the simplicity of the map, but the error handling of flatmap (not the verbosity). I haven't seen any best practices on this floating around and I'm curious how this is being used in practice.


map transform one event to another. flatMap transform one event to zero or more event. (this is taken from IntroToRx)

As you want to transform your json to an object, using map should be enough.

Dealing with the FileNotFoundException is another problem (using map or flatmap wouldn't solve this issue).

To solve your Exception problem, just throw it with a Non checked exception : RX will call the onError handler for you.

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // this exception is a part of rx-java
            throw OnErrorThrowable.addValueAsLastCause(e, file);
        }
    }
});

the exact same version with flatmap :

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            // this static method is a part of rx-java. It will return an exception which is associated to the value.
            throw OnErrorThrowable.addValueAsLastCause(e, file);
            // alternatively, you can return Obersable.empty(); instead of throwing exception
        }
    }
});

You can return too, in the flatMap version a new Observable that is just an error.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});

FlatMap behaves very much like map, the difference is that the function it applies returns an observable itself, so it's perfectly suited to map over asynchronous operations.

In the practical sense, the function Map applies just makes a transformation over the chained response (not returning an Observable); while the function FlatMap applies returns an Observable<T>, that is why FlatMap is recommended if you plan to make an asynchronous call inside the method.

Summary:

  • Map returns an object of type T
  • FlatMap returns an Observable.

A clear example can be seen here: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.X Client uses Rx to provide asynchronous calls in a convenient way. Since it uses Rx, it has the methods map and FlatMap, the explanation in their documentation might be helpful to understand the general concept.

To handle errors, override onError on your susbcriber.

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

It might help to look at this document: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

A good source about how to manage errors with RX can be found at: https://gist.github.com/daschl/db9fcc9d2b932115b679


In your case you need map, since there is only 1 input and 1 output.

map - supplied function simply accepts an item and returns an item which will be emitted further (only once) down.

flatMap - supplied function accepts an item then returns an "Observable", meaning each item of the new "Observable" will be emitted separately further down.

May be code will clear things up for you:

Observable.just("item1").map( str -> {
    System.out.println("inside the map " + str);
    return str;
}).subscribe(System.out::println);

Observable.just("item2").flatMap( str -> {
    System.out.println("inside the flatMap " + str);
    return Observable.just(str + "+", str + "++" , str + "+++");
}).subscribe(System.out::println);

Output:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++

The question is When do you use map vs flatMap in RxJava?. And I think a simple demo is more specific.

When you want to convert item emitted to another type , in your case converting file to String, map and flatMap can both work. But I prefer map operator because it's more clearly.

However in some place, flatMap can do magic work but map can't. For example, I want to get a user's info but I have to first get his id when user login in. Obviously I need two requests and they are in order.

Let's begin.

Observable<LoginResponse> login(String email, String password);

Observable<UserInfo> fetchUserInfo(String userId);

Here are two methods, one for login returned Response, and another for fetching user info.

login(email, password)
        .flatMap(response ->
                fetchUserInfo(response.id))
        .subscribe(userInfo -> {
            // get user info and you update ui now
        });

As you see, in function flatMap applies, at first I get user id from Response then fetch user info. When two requests are finished, we can do our job such as updating UI or save data into database.

However if you use map you can't write such nice code. In a word, flatMap can help us serialize requests.