Learning Rx by example (2)

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.

A487b8723907637cb1af973bc5957bb4?s=128

Kaushik Gopal

November 09, 2016
Tweet

Transcript

  1. 13.

    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();
 }
 });
  2. 14.

    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
  3. 16.

    Example 1 Code: Get database results but stop after network

    results getDiskResults() .takeUntil(getNetworkResults()) getDiskButStopAfterNetwork() =
  4. 17.

    Example 1 Code: Get disk results that occur before Network

    starts + Network results getDiskResults() .takeUntil(getNetworkResults()) getDiskButStopAfterNetwork() = getDiskButStopAfterNetwork() ) getNetworkResults() Observable .merge(
  5. 18.

    Example 1 Code: Get disk results that occur before Network

    starts + Network results ) .publish( getNetworkResults() getDiskResults() .takeUntil(getNetworkResults()) Observable .merge(
  6. 19.

    Example 1 Code: Get disk results that occur before Network

    starts + Network results Observable .merge( , ) .publish( getNetworkResults() network -> getNetworkResults() getDiskResults() .takeUntil(getNetworkResults())
  7. 20.

    Example 1 Code: Get disk results that occur before Network

    starts + Network results Observable .merge( , .publish( getNetworkResults() network -> network, ) getDiskResults() .takeUntil(getNetworkResults())
  8. 21.

    Example 1 Code: Get disk results that occur before Network

    starts + Network results Observable .merge( , .publish( getNetworkResults() network -> ) getDiskResults() .takeUntil(getNetworkResults()) network network,
  9. 22.

    Example 1 Code: Observable .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThrd())
 .subscribe(); .concat( getSlowCachedDiskData(), getFreshNetworkData())

    .publish getNetworkData()
 .publish( network ->
 Observable .merge( network, getDiskData() .takeUntil(network) ) )
  10. 24.

    Example 2b Setup starting seq. once
 (network call for results)

    Observable Observable
 .just(1)
 .flatMap(pageNo -> getNetworkResults(pageNo))
  11. 26.

    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);
  12. 27.

    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);
  13. 28.

    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?
  14. 29.

    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);
  15. 30.

    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);
  16. 32.

    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
  17. 33.

    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
  18. 35.

    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);
 }
 })
 }

  19. 39.

    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
  20. 40.

    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>>
  21. 41.

    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>>
  22. 42.

    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()
  23. 43.

    
 .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)) )
  24. 44.

    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
  25. 45.

    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
  26. 46.

    .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
  27. 48.

    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