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

RxJava for the rest of us

Hugo Visser
October 22, 2016

RxJava for the rest of us

Slides from the presentation given at Mobilization VI

Recording: https://www.youtube.com/watch?v=2NCoB2iqK3Q

Hugo Visser

October 22, 2016
Tweet

More Decks by Hugo Visser

Other Decks in Technology

Transcript

  1. // 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
  2. rx.Observable • Nothing is executed when you call this method!

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

    termination (onError) • Completion + termination (onComplete) public Observable<Article> article() {}
  4. 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 } });
  5. 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() { } });
  6. No longer interested s.unsubscribe(); • Always unsubscribe when done! (e.g.

    life cycle) • Memory leaks may occur otherwise • Don’t “lose” subscriptions
  7. 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.
  8. 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); }
  9. 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); }
  10. 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); }
  11. 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); }
  12. 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); }
  13. 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
  14. 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)) ); }
  15. 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)) ); }
  16. 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)) ); }
  17. 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)) ); }
  18. 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)) ); }
  19. 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)) ); }
  20. 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)); }
  21. 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)); }
  22. 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();
  23. 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();
  24. 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)); }
  25. 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(...);
  26. 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(); } }
  27. 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 -> { … })
  28. 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)); }); }
  29. 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 -> { … }));
  30. 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; });
  31. 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 -> {});
  32. 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 -> { … }, … );
  33. Conclusion • RxJava is a different way of doing things

    • Very powerful, but takes time and practice to learn • Take small steps learning it