Frozen Stawy Cietrzewia

flatMap()

concatMap()

concatMapEager()

Flowable<T>

T

Flowable<R>

R

Flowable<R>

Sample project

public interface GeoNames { Flowable<Long> populationOf(String city); }

String

Flowable<String> cities = Flowable.just( "Warsaw", "Paris", "London", "Madrid" );

concatMap() : process upstream sequentially

concatMap()

cities .concatMap(geoNames::populationOf) .subscribe(response -> log.info("Population: {}", response));

concatMap()

Long

Flowable<Long>

Long

Flowable<Flowable<Long>>

concatMap()

Flowable<Long>

Long

concatMap()

23:33:33.531 | Rx-1 | --> GET .../searchJSON?q=Warsaw http/1.1 23:33:33.656 | Rx-1 | <-- 200 OK .../searchJSON?q=Warsaw (123ms) 23:33:33.674 | Rx-1 | Population: 1702139 23:33:33.676 | Rx-1 | --> GET .../searchJSON?q=Paris http/1.1 23:33:33.715 | Rx-1 | <-- 200 OK .../searchJSON?q=Paris (38ms) 23:33:33.715 | Rx-1 | Population: 2138551 23:33:33.716 | Rx-1 | --> GET .../searchJSON?q=London http/1.1 23:33:33.754 | Rx-1 | <-- 200 OK .../searchJSON?q=London (37ms) 23:33:33.754 | Rx-1 | Population: 7556900 23:33:33.755 | Rx-1 | --> GET .../searchJSON?q=Madrid http/1.1 23:33:33.795 | Rx-1 | <-- 200 OK .../searchJSON?q=Madrid (40ms) 23:33:33.796 | Rx-1 | Population: 3255944

flatMap()

flatMap() : processing results on-the-fly, out-of-order

flatMap()

cities .flatMap(geoNames::populationOf) .subscribe(response -> log.info("Population: {}", response));

Long

Flowable<Flowable<Long>>

flatMap()

00:10:04.919 | Rx-2 | --> GET .../searchJSON?q=Paris http/1.1 00:10:04.919 | Rx-1 | --> GET .../searchJSON?q=Warsaw http/1.1 00:10:04.919 | Rx-3 | --> GET .../searchJSON?q=London http/1.1 00:10:04.919 | Rx-4 | --> GET .../searchJSON?q=Madrid http/1.1 00:10:05.449 | Rx-3 | <-- 200 OK .../searchJSON (529ms) 00:10:05.462 | Rx-3 | Population: 7556900 00:10:05.477 | Rx-1 | <-- 200 OK .../searchJSON (557ms) 00:10:05.478 | Rx-1 | Population: 1702139 00:10:05.751 | Rx-4 | <-- 200 OK .../searchJSON (831ms) 00:10:05.752 | Rx-4 | Population: 3255944 00:10:05.841 | Rx-2 | <-- 200 OK .../searchJSON (922ms) 00:10:05.843 | Rx-2 | Population: 2138551

concatMap()

flatMap()

concatMapEager() : concurrent, in-order, but somewhat expensive

concatMapEager()

cities .concatMapEager(geoNames::populationOf) .subscribe(response -> log.info("Population: {}", response));

concatMap()

flatMap()

concatMapEager()

concatMapEager()

00:34:18.371 | Rx-2 | --> GET .../searchJSON?q=Paris http/1.1 00:34:18.371 | Rx-3 | --> GET .../searchJSON?q=London http/1.1 00:34:18.371 | Rx-4 | --> GET .../searchJSON?q=Madrid http/1.1 00:34:18.371 | Rx-1 | --> GET .../searchJSON?q=Warsaw http/1.1 00:34:18.517 | Rx-3 | <-- 200 OK .../searchJSON?q=London (143ms) 00:34:18.563 | Rx-1 | <-- 200 OK .../searchJSON?q=Warsaw (189ms) 00:34:18.565 | Rx-1 | Population: 1702139 00:34:20.460 | Rx-2 | <-- 200 OK .../searchJSON?q=Paris (2086ms) 00:34:20.460 | Rx-4 | <-- 200 OK .../searchJSON?q=Madrid (2086ms) 00:34:20.461 | Rx-2 | Population: 2138551 00:34:20.462 | Rx-2 | Population: 7556900 00:34:20.462 | Rx-2 | Population: 3255944

concatMapEager()

concatMap()

flatMap()

concatMapEager()

concatMapEager()

Which operator to use?

flatMap()

concatMap()

concatMapEager()

Appendix: configuring Retrofit2 client

GeoNames

public interface GeoNames { @GET("/searchJSON") Single<SearchResult> search( @Query("q") String query, @Query("maxRows") int maxRows, @Query("style") String style, @Query("username") String username ); default Flowable<Long> populationOf(String city) { return search(city, 1, "LONG", "s3cret") .map(SearchResult::getGeonames) .map(g -> g.get(0)) .map(Geoname::getPopulation) .toFlowable(); } }

populationOf()

Flowable<Long>

SearchResult

class SearchResult { private List<Geoname> geonames = new ArrayList<>(); } class Geoname { private double lat; private double lng; private Integer geonameId; private Long population; private String countryCode; private String name; }

Maybe<Long>

default Maybe<Long> populationOf(String city) { return search(city, 1, "LONG", "nurkiewicz") .flattenAsFlowable(SearchResult::getGeonames) .map(Geoname::getPopulation) .firstElement(); }

import com.fasterxml.jackson.databind.ObjectMapper; private ObjectMapper objectMapper() { return new ObjectMapper() .configure(FAIL_ON_UNKNOWN_PROPERTIES, false); }

FAIL_ON_UNKNOWN_PROPERTIES

import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; private OkHttpClient client() { HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); return new OkHttpClient.Builder().addInterceptor(interceptor).build(); }

java.util.logging

import org.slf4j.bridge.SLF4JBridgeHandler; static { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); }

import io.reactivex.schedulers.Schedulers; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; GeoNames createClient() { return new Retrofit.Builder() .client(client()) .baseUrl("http://api.geonames.org") .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(JacksonConverterFactory.create(objectMapper())) .build() .create(GeoNames.class); }

createClient()

GeoNames

compile 'io.reactivex.rxjava2:rxjava:2.0.6' compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' compile 'com.squareup.retrofit2:converter-jackson:2.0.1' compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' compile 'ch.qos.logback:logback-classic:1.1.7' compile 'org.slf4j:slf4j-api:1.7.21' compile 'org.slf4j:jul-to-slf4j:1.7.21'