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

RxJava for the rest for us

RxJava for the rest for us

Hugo Visser

March 17, 2017
Tweet

More Decks by Hugo Visser

Other Decks in Technology

Transcript

  1. RxJava v1 vs v2 • Observable (no backpressure) vs Flowable

    (backpressured) • New group id (package): rx vs io.reactivex • Reactive Streams specification • Less allocations, larger method count • v1: feature freeze June 1st 2017, EOL March 31st 2018
  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. io.reactivex.Observable • Nothing is executed when you call this method!

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

    termination (onError) • Completion + termination (onComplete) public Observable<Article> article() {}
  5. Subscribing Disposable d = article().subscribeWith(new DisposableObserver<Article>() { @Override public void

    onNext(Article article) { // this can be called multiple times, never null as of 2.x! } @Override public void onError(Throwable e) { // no more events, and automatically unsubscribed } @Override public void onComplete() { // no more events, and automatically unsubscribed } });
  6. Subscribing Disposable d = article().subscribe(new Consumer<Article>() { @Override public void

    accept(Article article) { } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) { } }, new Action() { @Override public void run() { } });
  7. No longer interested d.dispose(); • Always unsubscribe when done! (e.g.

    life cycle) • Memory leaks may occur otherwise • Don’t “lose” subscriptions (disposables)
  8. public Config getConfig() {} public void applyConfig(Config config) {} public

    void init() { Config config = getConfig(); if (config != null) { applyConfig(config); } else { //TODO handle null config } }
  9. public Observable<Config> config() {} public void applyConfig(Config config) {} public

    void init() { //TODO handle errors! //TODO do we want to act on _every_ config update? //TODO dispose() at appropriate time mConfigDisposable = config().subscribe(this::applyConfig); }
  10. public Observable<Config> config() {} public void applyConfig(Config config) {} public

    void init() { mConfigDisposable = config().take(1). onErrorResumeNext(Observable.just(Config.DEFAULT)). subscribe(this::applyConfig); } public void destroy() { mConfigDisposable.dispose(); }
  11. 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.
  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. 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); }
  14. 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); }
  15. 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); }
  16. 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); }
  17. 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
  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 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)) ); }
  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(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)) ); }
  20. 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)) ); }
  21. 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)) ); }
  22. 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)) ); }
  23. 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)) ); }
  24. Getting there • How do I convert a stream of

    x to a stream of y? • Operators involved: filter, map, flatMap, groupBy, reduce • groupBy requires the stream to complete! (documentation)
  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 CompositeDisposable mDisposables = new CompositeDisposable(); @Override protected void onStart() { super.onStart(); mDisposables.add(createObservable().subscribe(...)); } @Override protected void onStop() { super.onStop(); // use clear(), not dispose() mDisposables.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()); } mDisposables.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; }); createObservable().doOnNext(s -> Log.d(TAG, "The value is "+s)). subscribe(this::processValue, throwable -> { … });
  31. Tips • Compose observables (aid testing, prevent huge chains) •

    Apply schedulers late as possible for better API // this gets boring and cumbersome retrieveArticle(). subscribeOn(Schedulers.io()). observeOn(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. Tips • RxJava 2 throws errors that v1 would swallow!

    • UndeliverableException → Observable life cycle error RxJavaPlugins.setErrorHandler(throwable -> { if (throwable instanceof UndeliverableException) { // log the exception somewhere, but don't crash return; } // what RxJava normally would do too Thread currentThread = Thread.currentThread(); Thread.UncaughtExceptionHandler handler = currentThread.getUncaughtExceptionHandler(); handler.uncaughtException(Thread.currentThread(), throwable); });
  34. Not every problem is a nail List<Article> articleList = getArticleList();

    Observable.from(articleList).subscribe(article -> { printArticle(article); doSomethingElse(article); });
  35. Conclusion • RxJava is a different way of doing things

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