Chaining Singles together in RxJava

Jtvd78 picture Jtvd78 · Feb 8, 2017 · Viewed 8k times · Source

I am using RxJava in my android app along with Retrofit to make network requests to a server. I am using RxJavaCallAdapterFactory so I can have my retrofit requests return singles. In my code, the retrofit object is named 'api'.

The code here works fine, but in this example, I need to retrieve the userId before I can make a playlist. I flat map the userId request to the API request, and after making the playlist, I need to use flat map again to convert the JSON response to a usable object.

public JSONUser me;

public Single<String> getUserId(){

    if(me != null){
        return Single.just(me.getUserId());
    }

    return api.getMe().flatMap(new Func1<JSONUser, Single<String>>() {
        @Override
        public Single<String> call(JSONUser meResult) {
            me = meResult;
            return Single.just(me.getUserId());
        }
    });
}

public Single<Playlist> createPlaylist(String name) {

    final NewPlaylistConfig config = new NewPlaylistConfig(name);

    return getUserId().flatMap(new Func1<String, Single<Playlist>>() {
        @Override
        public Single<Playlist> call(String userId) {
            return api.createPlaylist(userId, config).flatMap(
                    new Func1<JSONPlaylist, Single<? extends SpotifyPlaylist>>() {
                        @Override
                        public Single<? extends Playlist> call(JSONPlaylist data) {
                            return Single.just(new Playlist(data));
                    }
            });
        }
    });
}

The entry point here would be createPlaylist(). NewPlaylistConfig will be converted to JSON and is simply the body parameter for the POST request. UserId is needed as a path parameter.

My main question here, is if there is a way to chain these operations without the "callback-hell" you see here. Like I said, this code works but it is really ugly. I would appreciate if somebody could point me in the right direction regarding this. Is there a way to make this work like promises where you can just chain .thens?

Thank you.

Answer

Geoffrey Marizy picture Geoffrey Marizy · Feb 8, 2017

Instead of:

getUserId()
    .flatMap(new Func1<String, Single<Playlist>>() {
        @Override
        public Single<Playlist> call(String userId) {
            return api.createPlaylist(userId, config).flatMap(
                new Func1<JSONPlaylist, Single<? extends SpotifyPlaylist>>() {
                    @Override
                    public Single<? extends Playlist> call(JSONPlaylist data) {
                        return Single.just(new Playlist(data));
                    }
            });
        }
     });

write:

getUserId()
    .flatMap(new Func1<String, Single<Playlist>>() {
        @Override
        public Single<Playlist> call(String userId) {
            return api.createPlaylist(userId, config);
        }
    })
    .flatMap(
        new Func1<JSONPlaylist, Single<? extends SpotifyPlaylist>>() {
            @Override
            public Single<? extends Playlist> call(JSONPlaylist data) {
                return Single.just(new Playlist(data));
            }
    });

Actually you can chain flatmap (and map) like you chain then with promises.


Note you can replace the second flatmap with map, replacing:

return Single.just(new Playlist(data));

with:

return new Playlist(data);

Result :

getUserId()
    .flatMap(new Func1<String, Single<Playlist>>() {
        @Override
        public Single<Playlist> call(String userId) {
            return api.createPlaylist(userId, config);
        }
    })
    .map(
        new Func1<JSONPlaylist, SpotifyPlaylist>() {
            @Override
            public SpotifyPlaylist call(JSONPlaylist data) {
                return new Playlist(data);
            }
    });

Edit: you can go further with java 8:

getUserId()
    .flatMap(userId -> api.createPlaylist(userId, config))
    .map(data -> new Playlist(data));

Or Kotlin:

getUserId()
    .flatMap { api.createPlaylist(it, config) }
    .map { Playlist(it) }