Skip to content

Instantly share code, notes, and snippets.

@aldoKelvianto
Created December 5, 2016 19:18
Show Gist options
  • Save aldoKelvianto/f06ac408c26e0c6e9ccf0bd7188d7d01 to your computer and use it in GitHub Desktop.
Save aldoKelvianto/f06ac408c26e0c6e9ccf0bd7188d7d01 to your computer and use it in GitHub Desktop.
How to chain two API calls (or more) in Rx?

A friend of mine basically ask me this question, "How to chain two API calls in Rx?". At first I thought it would be a quick and easy code. And I was wrong, in the end I spent more time than I should, so I hope this post would help.

Let's say we have this API and we want to get weather for every city.

public interface WeatherApi {

    @GET("cities")
    Observable<List<City>> getCities(); 
    // Query params is omitted for simplicty, usually lat and lan

    @GET("weather")
    Observable<Weather> getWeather(int cityId);
}

Now, let's get list of cities.

getCities().subscribe(
    new Subscriber<List<City>>(){
        public void onCompleted() {                        
        }

        public void onError(Throwable e) {
        }

        public void onNext(List<City> cities) {

        }

    }
);

After we get list of cities, how do we get weather? Do we have to call getWeather one at a time using for loop? Something like this?

getCities().subscribe(
    new Subscriber<List<City>>(){
        public void onCompleted() {                        
        }

        public void onError(Throwable e) {
        }

        public void onNext(List<City> cities) {
            for(City city : cities){
                getWeather(city.getId).subscribe(
                    new Subscriber<Weather>(){
                        public void onCompleted() {                                                
                        }

                        public void onError(Throwable e) {
                        }
                                
                        public void onNext(Weather weather){
                            // Do something with the weather
                        }
                );
            }
        }
    }
);
    

...That doesn't seems right.

So, how do we fix this? First, we need to emit cities one by one, you can use Observable.from() for that, but how do we transform City to Weather?

It might me tempting to use Map, but map doesn't return Observable. That's where flatMap and concatMap is created.

The difference is, concatMap is just an ordererd flatMap. Fernando Cejas has good app to illustrate this.

Now, let's use concatMap to solve this problem.

getCities()
    .concatMap(new Func1<List<City>, Observable<City>>(){
      public Observable<City> call(List<City> cities){
          return Observable.from(cities);
      }  
    })
    .concatMap(new Func1<City, Observable<Weather>>(){
        public Observable<Weather> call(City city){
            return getWeather(city.getId());
        }
    })
    .subscribe(
        new Subscriber<List<City>>(){
            public void onCompleted() {                        
            }

            public void onError(Throwable e) {
            }

            public void onNext(Weather weather) {
                // Do something for weather
            }
    }
);

As you can see, your Subscriber is now reading Weather. It's much cleaner and maintainable this way. Using this approach, you can add more and more API endpoint with single Subscriber.

That's it! Rx is fun!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment