Share
Embed
Learning Rx by example (2)
Given at Oredev 2016: https://vimeo.com/190922794
(For a beginner's version of this talk check here: https://www.youtube.com/watch?v=k3D0cWyNno4)
Rx (Reactive extensions) is a powerful API for Asynchronous programming.
It has a steep learning curve. Surprisingly though, the easiest way to grasp the concepts is by examples. So in this talk, we'll look at just 3 examples.
These are marginally complex requirements that most developers would run into these days. We dissect the problem using Rx and try to come up with elegant and simple solutions to an otherwise complicated problem.
Kaushik Gopal
November 09, 2016
Transcript
49 slides
-
Observer pattern done right Best ideas from: Observer pattern Iterator
pattern Functional programming -
.concatEager( .merge( Example 1 Code: Observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThrd()) .subscribe(); .concatEager
getDiskResults(), getNetworkResults()) .merge -
.concatEager( .merge( Example 1 Code: Observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThrd()) .subscribe(); .concatEager
getDiskResults(), getNetworkResults()) .merge Both of these methods return Observable<Result> -
Example 1 Code: .merge Observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThrd()) .subscribe(); .merge( getDiskResults(),
getNetworkResults()) .subscribe(new Subscriber<Result>() { @Override public void onCompleted() { //... } @Override public void onError(Throwable e) { //... } @Override public void onNext(Result result) { if ( list.contains(result) && isExistingResultFromNetwork(result)) return; // usual "add result to list" logic list.add(result); list.refresh(); } }); -
Example 1 Code: .merge Observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThrd()) .subscribe(); .merge( getDiskResults(),
getNetworkResults()) .subscribe(new Subscriber<Result>() { @Override public void onCompleted() { //... } @Override public void onError(Throwable e) { //... } @Override public void onNext(Result result) { if ( list.contains(result) && isResultFromNetwork(result)) return; // usual "add result to list" logic list.add(result); list.refresh(); } }); NO! The Rx is not strong with this code https://twitter.com/JakeWharton/status/786363146990649345 -
Example 1 Code: getNetworkData() .publish( network -> ) getDiskData() .takeUntil(network)
Observable .merge( network, ) -
Example 1 Code: Get database results but stop after network
results getDiskResults() .takeUntil(getNetworkResults()) getDiskButStopAfterNetwork() = -
Example 1 Code: Get disk results that occur before Network
starts + Network results getDiskResults() .takeUntil(getNetworkResults()) getDiskButStopAfterNetwork() = getDiskButStopAfterNetwork() ) getNetworkResults() Observable .merge( -
Example 1 Code: Get disk results that occur before Network
starts + Network results ) .publish( getNetworkResults() getDiskResults() .takeUntil(getNetworkResults()) Observable .merge( -
Example 1 Code: Get disk results that occur before Network
starts + Network results Observable .merge( , ) .publish( getNetworkResults() network -> getNetworkResults() getDiskResults() .takeUntil(getNetworkResults()) -
Example 1 Code: Get disk results that occur before Network
starts + Network results Observable .merge( , .publish( getNetworkResults() network -> network, ) getDiskResults() .takeUntil(getNetworkResults()) -
Example 1 Code: Get disk results that occur before Network
starts + Network results Observable .merge( , .publish( getNetworkResults() network -> ) getDiskResults() .takeUntil(getNetworkResults()) network network, -
Example 1 Code: Observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThrd()) .subscribe(); .concat( getSlowCachedDiskData(), getFreshNetworkData())
.publish getNetworkData() .publish( network -> Observable .merge( network, getDiskData() .takeUntil(network) ) ) -
Example 2b Setup starting seq. once (network call for results)
Observable Observable .just(1) .flatMap(pageNo -> getNetworkResults(pageNo)) -
compose Observable<R> Observable<T> map R T flatmap R Observable<T> Holy
trinity of RxConversion -
Example 2b Setup starting seq. once (network call for results)
Subscribe here Observable Observable .just(1) .flatMap(pageNo -> getNetworkResults(pageNo)) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { // all items downloaded } @Override public void onError(Throwable e) { // handle error } @Override public void onNext(List<Item> items) { } }); private Observable<List<Item>> getNetworkResults(int pageNo) { // make network request // get List of Items for page number } addToList(items); -
Example 2b Setup starting seq. once (network call for results)
Subscribe here Observable Observable .just(1) .flatMap(pageNo -> getNetworkResults(pageNo)) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { // all items downloaded } @Override public void onError(Throwable e) { // handle error } @Override public void onNext(List<Item> items) { } }); .concatMap(pageNo -> getNetworkResults(pageNo)) addToList(items); -
Example 2b Setup starting seq. once (network call for results)
Subscribe here like page numbers! Observable What if I could keep feeding inputs to get the next set of results? -
Network call to get results for page n Subject
Code: PublishSubject<Integer> paginator = PublishSubject.create(); Example 2b Observable .just(1) .concatMap(pgNo -> getNetworkResults(pgNo)) paginator .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { // all items downloaded } @Override public void onError(Throwable e) { // handle error } @Override public void onNext(List<Item> items) { } }); addToList(items); -
Network call to get results for page n Subscribe
here Example 2a Code: Subject void onReachedEndOfList() { //... } paginator.onNext(nextPage); .concatMap(pgNo -> getNetworkResults(pgNo)) paginator .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { // all items downloaded } @Override public void onError(Throwable e) { // handle error } @Override public void onNext(List<Item> items) { } }); addToList(items); -
Example 3 TrueTime NTP Pool DNS Resolve SNTP Rq
2 SNTP Request 1 SNTP Rq 3 SNTP Rq 4 SNTP Rq 5 Least roundtrip delay 5 IP1 IP2 IP3 IP4 Sort by clock offset + Pick Median -
Example 3 TrueTime NTP Pool DNS Resolve SNTP Rq
2 SNTP Request 1 SNTP Rq 3 SNTP Rq 4 SNTP Rq 5 Least roundtrip delay 5 IP1 IP2 IP3 IP4 TrueTime Sort by clock offset + Pick Median -
Observable .just(ntpPool) .compose(resolveNtpPool()) NTP DNS Resolve time.apple.com Code: compose Observable<Type1>
Observable<Type 2> IP1 IP2 IP3 IP4 -
Observable .just(ntpPool) .compose(resolveNtpPool()) NTP DNS Resolve time.apple.com Code: private Transformer<String,
String> resolveNtpPool(){ return ntpPool -> { try { return Observable.from( InetAddress.getAllByName(ntpPool)); } catch (UnknownHostException e) { return Observable.error(e); } }) } -
Code: Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) IP1 T (IP1) Observable<R> (SNTP
response) Request 1 SNTP Request 2 SNTP Request 3 SNTP Request 4 SNTP Request 5 SNTP -
compose Observable<R> Observable<T> map R T flatmap T Observable<T> Holy
trinity of RxConversion -
Code: IP1 Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) bestResponseAgainstSingleIp() Func1 <String, Observable<Response>>
return Observable .just(singleIp) .repeat(5) { Request 1 SNTP Request 2 SNTP Request 3 SNTP Request 4 SNTP Request 5 SNTP -
Code: .flatMap(ipAddress -> sntpNtwrkReq(ipAddress)) Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) bestResponseAgainstSingleIp() return
Observable .just(singleIp) .repeat(5) { IP1 Request 1 SNTP Request 2 SNTP Request 3 SNTP Request 4 SNTP Request 5 SNTP Func1 <String, Observable<Response>> -
Code: IP1 Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) bestResponseAgainstSingleIp() return Observable .just(singleIp)
.repeat(5) { 5 .flatMap(ipAddress -> sntpNtwrkReq(ipAddress)) Request 1 SNTP Request 2 SNTP Request 3 SNTP Request 4 SNTP Request 5 SNTP .doOnError(throwable -> { // this request alone failed // retry this req alone }) .retry(5)) } Func1 <String, Observable<Response>> -
Code: IP1 Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) bestResponseAgainstSingleIp() return Observable .just(singleIp)
.repeat(5) { 5 .flatMap(ipAddress -> sntpNtwrkReq(ipAddress)) Request 1 SNTP Request 2 SNTP Request 3 SNTP Request 4 SNTP Request 5 SNTP } Func1 <String, Observable<Response>> .doOnError(throwable -> { // this request alone failed // retry this req alone }) .retry(5)) ) .toList() -
.map(responseList -> { Collections.sort(responseList, comparator); return responseList.get(0); })
Least roundtrip delay Code: .toList() Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) bestResponseAgainstSingleIp() Func1 <String, Observable<Response>> { } .doOnError(throwable -> { // this request alone failed // retry this req alone }) .retry(5)) ) -
Example 3 TrueTime NTP Pool DNS Resolve SNTP Rq
2 SNTP Request 1 SNTP Rq 3 SNTP Rq 4 SNTP Rq 5 Least roundtrip delay 5 IP1 IP2 IP3 IP4 Sort by clock offset + Pick Median -
Example 3 TrueTime NTP Pool DNS Resolve SNTP Rq
2 SNTP Request 1 SNTP Rq 3 SNTP Rq 4 SNTP Rq 5 5 IP1 IP2 IP3 IP4 Sort by clock offset + Pick Median -
.map(filterMedianResponse()) Code: Observable .just(ntpPool) .compose(resolveNtpPool()) .flatMap(bestResponseAgainstSingleIp()) .toList() filterMedianResponse() Func1 <List<Response>,
Response> return responseList -> { } Collections.sort(responses, comparator); Sort by clock offset return bestResponses .get(bestResponses.size() / 2); Pick Median Sort by clock offset + Pick Median -
Example 3 TrueTime NTP Pool DNS Resolve SNTP Rq
2 SNTP Request 1 SNTP Rq 3 SNTP Rq 4 SNTP Rq 5 Least roundtrip delay 5 IP1 IP2 IP3 IP4 TrueTime Sort by clock offset + Pick Median -
✌ fragmentedpodcast.com tech.instacart.com @kaushikgopal kaush.co My thanks to @cyrilmotier who
graciously allowed me to rip-off his slide deck theme