Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Kaushik Gopal

November 09, 2016
Tweet

More Decks by Kaushik Gopal

Other Decks in Programming

Transcript

  1. Learning Rx

    by example

    View full-size slide

  2. 2 mt intro to Rx
    3 (intermediate) examples
    1

    2

    View full-size slide

  3. 2 mt
    Intro to Rx

    View full-size slide

  4. API for
    Asynchronous programming

    View full-size slide

  5. Observer pattern done right
    Best ideas from:
    Observer pattern
    Iterator pattern
    Functional programming

    View full-size slide

  6. Observer pattern done right
    Observable.just(1)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(getSubscriber());
    .useSomeFunkyOperators()

    View full-size slide

  7. Example 1
    Loading from
    Disk Cache + Network Call

    View full-size slide

  8. Cached data

    Fast
    Example 1
    Database
    Fresh data

    Slow
    Server
    Requirement:

    View full-size slide

  9. .concatEager(
    .merge(
    Example 1
    Code:
    Observable
    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThrd())

    .subscribe();
    .concatEager
    getDiskResults(),
    getNetworkResults())
    .merge

    View full-size slide

  10. .concatEager(
    .merge(
    Example 1
    Code:
    Observable
    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThrd())

    .subscribe();
    .concatEager
    getDiskResults(),
    getNetworkResults())
    .merge
    Both of these methods
    return
    Observable

    View full-size slide

  11. Example 1
    Database Network request
    Problem:
    .merge

    View full-size slide

  12. Example 1
    Database Network request
    Problem:
    .merge

    View full-size slide

  13. Example 1
    Code:
    .merge
    Observable
    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThrd())

    .subscribe();
    .merge(
    getDiskResults(),
    getNetworkResults())
    .subscribe(new Subscriber() {

    @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();

    }

    });

    View full-size slide

  14. Example 1
    Code:
    .merge
    Observable
    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThrd())

    .subscribe();
    .merge(
    getDiskResults(),
    getNetworkResults())
    .subscribe(new Subscriber() {

    @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

    View full-size slide

  15. Example 1
    Code:
    getNetworkData()
    .publish(
    network ->
    )
    getDiskData()
    .takeUntil(network)
    Observable
    .merge(
    network,
    )

    View full-size slide

  16. Example 1
    Code:
    Get database results
    but stop after network results
    getDiskResults()
    .takeUntil(getNetworkResults())
    getDiskButStopAfterNetwork()
    =

    View full-size slide

  17. Example 1
    Code:
    Get disk results that occur before Network starts
    +
    Network results
    getDiskResults()
    .takeUntil(getNetworkResults())
    getDiskButStopAfterNetwork()
    =
    getDiskButStopAfterNetwork()
    )
    getNetworkResults()
    Observable
    .merge(

    View full-size slide

  18. Example 1
    Code:
    Get disk results that occur before Network starts
    +
    Network results
    )
    .publish(
    getNetworkResults()
    getDiskResults()
    .takeUntil(getNetworkResults())
    Observable
    .merge(

    View full-size slide

  19. Example 1
    Code:
    Get disk results that occur before Network starts
    +
    Network results
    Observable
    .merge(
    ,
    )
    .publish(
    getNetworkResults()
    network ->
    getNetworkResults()
    getDiskResults()
    .takeUntil(getNetworkResults())

    View full-size slide

  20. Example 1
    Code:
    Get disk results that occur before Network starts
    +
    Network results
    Observable
    .merge(
    ,
    .publish(
    getNetworkResults()
    network ->
    network,
    )
    getDiskResults()
    .takeUntil(getNetworkResults())

    View full-size slide

  21. Example 1
    Code:
    Get disk results that occur before Network starts
    +
    Network results
    Observable
    .merge(
    ,
    .publish(
    getNetworkResults()
    network ->
    )
    getDiskResults()
    .takeUntil(getNetworkResults())
    network
    network,

    View full-size slide

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

    View full-size slide

  23. Example 2
    Pagination

    (using Subjects)

    View full-size slide

  24. Example 2b
    Setup starting seq. once

    (network call for results)
    Observable
    Observable

    .just(1)

    .flatMap(pageNo -> getNetworkResults(pageNo))

    View full-size slide

  25. compose
    Observable Observable
    map
    R T
    flatmap
    R Observable
    Holy trinity of RxConversion

    View full-size slide

  26. Example 2b
    Setup starting seq. once

    (network call for results)
    Subscribe
    here
    Observable
    Observable

    .just(1)

    .flatMap(pageNo -> getNetworkResults(pageNo))
    .subscribe(new Subscriber>() {

    @Override

    public void onCompleted() {

    // all items downloaded

    }


    @Override

    public void onError(Throwable e) {

    // handle error

    }


    @Override

    public void onNext(List items) {


    }

    });
    private Observable> getNetworkResults(int pageNo) {

    // make network request
    // get List of Items for page number

    }
    addToList(items);

    View full-size slide

  27. Example 2b
    Setup starting seq. once

    (network call for results)
    Subscribe
    here
    Observable
    Observable

    .just(1)

    .flatMap(pageNo -> getNetworkResults(pageNo))
    .subscribe(new Subscriber>() {

    @Override

    public void onCompleted() {

    // all items downloaded

    }


    @Override

    public void onError(Throwable e) {

    // handle error

    }


    @Override

    public void onNext(List items) {


    }

    });
    .concatMap(pageNo -> getNetworkResults(pageNo))
    addToList(items);

    View full-size slide

  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?

    View full-size slide

  29. Network call 

    to get results for page n

    Subject
    Code:
    PublishSubject paginator =
    PublishSubject.create();
    Example 2b
    Observable

    .just(1)

    .concatMap(pgNo -> getNetworkResults(pgNo))
    paginator
    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(new Subscriber>() {

    @Override

    public void onCompleted() {

    // all items downloaded

    }


    @Override

    public void onError(Throwable e) {

    // handle error

    }


    @Override

    public void onNext(List items) {


    }

    });
    addToList(items);

    View full-size slide

  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>() {

    @Override

    public void onCompleted() {

    // all items downloaded

    }


    @Override

    public void onError(Throwable e) {

    // handle error

    }


    @Override

    public void onNext(List items) {


    }

    });
    addToList(items);

    View full-size slide

  31. Example 3
    TrueTime: Implementing NTP with Rx
    github.com/instacart/truetime-android

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  34. Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    NTP
    DNS

    Resolve
    time.apple.com
    Code:
    compose
    Observable Observable
    IP1
    IP2
    IP3
    IP4

    View full-size slide

  35. Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    NTP
    DNS

    Resolve
    time.apple.com
    Code:
    private Transformer resolveNtpPool(){

    return ntpPool -> {

    try {

    return Observable.from(
    InetAddress.getAllByName(ntpPool));
    } catch (UnknownHostException e) {

    return Observable.error(e);

    }

    })

    }


    View full-size slide

  36. Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    NTP
    DNS

    Resolve
    time.apple.com
    Code:
    IP1

    View full-size slide

  37. Code:
    Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    .flatMap(bestResponseAgainstSingleIp())

    IP1
    T (IP1) Observable (SNTP response)
    Request 1
    SNTP
    Request 2
    SNTP
    Request 3
    SNTP
    Request 4
    SNTP
    Request 5
    SNTP

    View full-size slide

  38. compose
    Observable Observable
    map
    R T
    flatmap
    T Observable
    Holy trinity of RxConversion

    View full-size slide

  39. Code:
    IP1
    Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    .flatMap(bestResponseAgainstSingleIp())

    bestResponseAgainstSingleIp()
    Func1 >
    return Observable
    .just(singleIp)

    .repeat(5)
    {
    Request 1
    SNTP
    Request 2
    SNTP
    Request 3
    SNTP
    Request 4
    SNTP
    Request 5
    SNTP

    View full-size slide

  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 >

    View full-size slide

  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 >

    View full-size slide

  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 >

    .doOnError(throwable -> {

    // this request alone failed
    // retry this req alone

    })

    .retry(5))
    )

    .toList()

    View full-size slide


  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 >
    {
    }

    .doOnError(throwable -> {

    // this request alone failed
    // retry this req alone

    })

    .retry(5))
    )

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  46. .map(filterMedianResponse())
    Code: Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    .flatMap(bestResponseAgainstSingleIp())

    .toList()
    filterMedianResponse()
    Func1 , 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

    View full-size slide

  47. Observable

    .just(ntpPool)

    .compose(resolveNtpPool())

    .flatMap(bestResponseAgainstSingleIp())

    .doOnNext(response -> convertToTime(response));
    .map(filterMedianResponse())
    .toList()
    Code:
    TrueTime
    Sort by
    clock offset
    +
    Pick
    Median

    View full-size slide

  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

    View full-size slide


  49. fragmentedpodcast.com
    tech.instacart.com
    @kaushikgopal
    kaush.co
    My thanks to @cyrilmotier
    who graciously allowed me to rip-off his slide deck theme

    View full-size slide