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

RXJAVA FOR THE REST OF US - HUGO VISSER

1fa9cb8c7997c8c4d3d251fb5e41f749?s=47 Realm
October 22, 2016

RXJAVA FOR THE REST OF US - HUGO VISSER

The Reactive Extensions for the JVM, better known as RxJava, have become a popular tool in Android development over the past years. Many new and existing libraries added support for Rx and “reactive” solutions to existing problems popped up over time. RxJava is both known for it’s power and it’s steep learning curve, so if you aren’t using RxJava already, what are you missing out on? Should you even be using RxJava? In this talk we’ll go over what RxJava is and how you can use it to solve problems in your Android apps, one step at a time. We will discuss how you can prevent shooting yourself in the foot by looking at common mistakes you may make when using RxJava and discussing tips and tricks for keeping your reactive app manageable.

1fa9cb8c7997c8c4d3d251fb5e41f749?s=128

Realm

October 22, 2016
Tweet

Transcript

  1. RxJava for the rest of us Hugo Visser @botteaap hugo@littlerobots.nl

  2. // Everything is awesome and simple! (pulling the result) public

    Article getArticle() throws Exception {} // Asynchronous callback (awaiting the result) public void getArticle(ArticleCallback callback) {} // Asynchronous execution w/ callback on main thread public AsyncTask<Void, Void, Article> fetchArticle() {} Typical code
  3. rx.Observable • Nothing is executed when you call this method!

    Pass it around! • Synchronous or asynchronous (& cancelable) • Error handling public Observable<Article> article() {}
  4. rx.Observable - “The stream” • Value (onNext) • Error +

    termination (onError) • Completion + termination (onComplete) public Observable<Article> article() {}
  5. Subscription Subscription s = article().subscribe(new Subscriber<Article>() { @Override public void

    onNext(Article article) { // this can be called multiple times } @Override public void onError(Throwable e) { // no more events, and automatically unsubscribed } @Override public void onCompleted() { // no more events, and automatically unsubscribed } });
  6. Subscription Subscription s = article().subscribe(new Action1<Article>() { @Override public void

    call(Article article) { } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { } }, new Action0() { @Override public void call() { } });
  7. Subscription (lambda expressions) Subscription s = article().subscribe( article -> {

    ... }, error -> { ... }, () -> { ... });
  8. No longer interested s.unsubscribe(); • Always unsubscribe when done! (e.g.

    life cycle) • Memory leaks may occur otherwise • Don’t “lose” subscriptions
  9. Steep learning curve?

  10. Architectural change A different way requires different thinking

  11. Architectural change Stream of events vs a single value

  12. Architectural change Does this stream ever complete?

  13. Architectural change Am I handling errors correctly?

  14. Functional style Immutable types, chaining of operations

  15. Operators Lots of them!

  16. Documentation Might be daunting (generics, marble diagrams)

  17. None
  18. Gentle start • Don’t try to go all out “reactive”

    • Apply where it makes sense ◦ Domain layer / business logic ◦ REST calls (Retrofit) ◦ When coping with life cycle ◦ More specialised event bus • UI, maybe, but save it for later and/or specific cases.
  19. Convert from Article to rfidTag string public Observable<String> rfIdTags(Observable<Article> observable)

    { // map = convert an object to another object return observable.map(article -> article.rfIdTag); }
  20. Convert from Article to rfidTag string public Observable<String> rfIdTags(Observable<Article> observable)

    { // map = convert an object to another object return observable.map(article -> article.rfIdTag); }
  21. Convert from Article to rfidTag string public Observable<String> rfIdTags(Observable<Article> observable)

    { // map = convert an object to another object return observable.map(article -> article.rfIdTag); }
  22. Convert from Article to rfidTag string public Observable<String> rfIdTags(Observable<Article> observable)

    { // map = convert an object to another object return observable.map(article -> article.rfIdTag); }
  23. Convert from Article to rfidTag string public Observable<String> rfIdTags(Observable<Article> observable)

    { // map = convert an object to another object return observable.map(article -> article.rfIdTag); }
  24. Convert from Article to rfidTag string Observable<String> newObservable = observable.map(article

    -> article.rfIdTag);
  25. Count (group) same articles 1. Only proces articles that are

    supposed to be in stock 2. Group all articles by the article id, derived from the rfid tag 3. Count all articles in the group 4. Emit one ArticleQuantity object per group
  26. Count (group) same articles public Observable<ArticleQuantity> quantity(Observable<Article> observable) { return

    observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable flatMap(group -> // each item in the group is converted to ArticleQuantity group.map(articleInfo -> new ArticleQuantity(group.getKey(), 1)). // then these items are reduced to a single item per group reduce((q1, q2) -> new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity)) ); }
  27. Count (group) same articles public Observable<ArticleQuantity> quantity(Observable<Article> observable) { return

    observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable flatMap(group -> // each item in the group is converted to ArticleQuantity group.map(article -> new ArticleQuantity(group.getKey(), 1)). // then these items are reduced to a single item per group reduce((q1, q2) -> new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity)) ); }
  28. Count (group) same articles public Observable<ArticleQuantity> quantity(Observable<Article> observable) { return

    observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable flatMap(group -> // each item in the group is converted to ArticleQuantity group.map(article -> new ArticleQuantity(group.getKey(), 1)). // then these items are reduced to a single item per group reduce((q1, q2) -> new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity)) ); }
  29. None
  30. Count (group) same articles public Observable<ArticleQuantity> quantity(Observable<Article> observable) { return

    observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable // group type: Observable<GroupObservable<String,Article>> flatMap(group -> // GroupObservable<String, Article> // each item in the group is converted to ArticleQuantity group.map(article -> new ArticleQuantity(group.getKey(), 1)). // then these items are reduced to a single item per group reduce((q1, q2) -> new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity)) ); }
  31. Count (group) same articles public Observable<ArticleQuantity> quantity(Observable<Article> observable) { return

    observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable // group type: Observable<GroupObservable<String,Article>> flatMap(group -> // GroupObservable<String, Article> // each item in the group is converted to ArticleQuantity group.map(article -> new ArticleQuantity(group.getKey(), 1)). // then these items are reduced to a single item per group reduce((q1, q2) -> new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity)) ); }
  32. Count (group) same articles public Observable<ArticleQuantity> quantity(Observable<Article> observable) { return

    observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable flatMap(group -> // each item in the group is converted to ArticleQuantity group.map(articleInfo -> new ArticleQuantity(group.getKey(), 1)). // then these items are reduced to a single item per group reduce((q1, q2) -> new ArticleQuantity(q1.articleId, q1.quantity + q2.quantity)) ); }
  33. Combine the list of all rfidTags and the expected ArticleQuantities

    Expected stock Observable<ExpectedStock> expectedStock(Observable<Article> articles) { Observable<Article> observable = articles.publish().autoConnect(2); return Observable.combineLatest(rfIdTags(observable).toList(), quantity(observable).toList(), (rfidTags, quantities) -> new ExpectedStock(rfidTags, quantities)); }
  34. Combine the list of all rfidTags and the expected ArticleQuantities

    Expected stock Observable<ExpectedStock> expectedStock(Observable<Article> articles) { Observable<Article> observable = articles.publish().autoConnect(2); return Observable.combineLatest(rfIdTags(observable).toList(), quantity(observable).toList(), (rfidTags, quantities) -> new ExpectedStock(rfidTags, quantities)); }
  35. Postpone subscription to the source until connect() is called ConnectableObservable

    ConnectableObservable<Article> connectable = observable.publish(); Subscription s1 = connectable.subscribe(); Subscription s2 = connectable.subscribe(); connectable.connect();
  36. Connect only after a specific number of subscriptions AutoConnect ConnectableObservable<Article>

    connectable = observable.publish().autoConnect(2); Subscription s1 = connectable.subscribe(); Subscription s2 = connectable.subscribe(); // happens automatically now // connectable.connect();
  37. Combine the list of all rfidTags and the expected ArticleQuantities

    Expected stock Observable<ExpectedStock> expectedStock(Observable<Article> articles) { Observable<Article> observable = articles.publish().autoConnect(2); return Observable.combineLatest(rfIdTags(observable).toList(), quantity(observable).toList(), (rfidTags, quantities) -> new ExpectedStock(rfidTags, quantities)); }
  38. Schedulers (threading) • Schedulers.io(), Schedulers.computation(), Schedulers.from(executor) … • AndroidSchedulers.mainThread() from

    rxandroid • subscribeOn() and observeOn() operators
  39. Schedulers (threading) • subscribeOn controls the thread when subscribed (apply

    once!) • observeOn controls the thread for events • Operators with a default scheduler, like timer() observable.map(...).subscribeOn(io()).observeOn(mainThread()).flatMap(...);
  40. Common mistakes

  41. Subscriptions • Not keeping references → unable to cancel +

    possible memory leaks public class MyActivity extends Activity { private CompositeSubscription mSubscriptions = new CompositeSubscription(); @Override protected void onStart() { super.onStart(); mSubscriptions.add(createObservable().subscribe(...)); } @Override protected void onStop() { super.onStop(); // use clear(), not unsubscribe() mSubscriptions.clear(); } }
  42. Subscriptions • Error handling (or lack thereof) • Always log

    to prevent swallowing unexpected errors • rxlint lint check can help and flag missing error handlers • // Oops, no error handling! createObservable().subscribe(value -> { … }) // errors are handled here createObservable().subscribe(value -> { … }, error -> { … })
  43. Subscriptions • Rock it like it’s AsyncTask public void fetchValueFromTheNetwork()

    { createObservable().subscribeOn(Schedulers.io()). observeOn(AndroidSchedulers.mainThread()). subscribe(value -> { EventBus.getDefault().post(new ValueEvent(value)); }, error -> { EventBus.getDefault().post(new ValueErrorEvent(error)); }); }
  44. Subscriptions • Rock it like it’s AsyncTask public Observable<String> fetchValueFromTheNetwork()

    { return createObservable().subscribeOn(Schedulers.io()). observeOn(AndroidSchedulers.mainThread()); } mSubscriptions.add(fetchValueFromTheNetwork().subscribe(value -> { … }, error -> { … }));
  45. Breaking the stream • For side effects use doOnXXX() •

    Don’t subscribe() in an operator, but flatMap createObservable().map(value -> { // this is not the place to subscribe or call // other functions. createAnotherObservable().subscribe( … ); return value; });
  46. Tips • Compose observables (aid testing, prevent huge chains) •

    Apply schedulers late as possible for better API // this gets boring and cumbersome retrieveArticle(). observeOn(Schedulers.io()). subscribeOn(AndroidSchedulers.mainThread()). subscribe(article -> {});
  47. Tips • Compose observables (aid testing, prevent huge chains) •

    Apply schedulers late as possible for better API /** * Retrieve a single article. Will return the result on the main thread * @return the observable */ public Observable<Article> retrieveArticle() { return articleHttpCall(). subscribeOn(Schedulers.io()). observeOn(AndroidSchedulers.mainThread()); } // does the right thing retrieveArticle().subscribe(article -> { … }, … );
  48. Conclusion • RxJava is a different way of doing things

    • Very powerful, but takes time and practice to learn • Take small steps learning it
  49. onCompleted() @botteaap hugo@littlerobots.nl