Slide 1

Slide 1 text

RxJava (2) for the rest of us Hugo Visser @botteaap [email protected]

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

// 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 fetchArticle() {} Typical code

Slide 4

Slide 4 text

io.reactivex.Observable ● Nothing is executed when you call this method! Pass it around! ● Synchronous or asynchronous (& cancelable) ● Error handling public Observable article() {}

Slide 5

Slide 5 text

io.reactivex.Observable - “The stream” ● Value (onNext) ● Error + termination (onError) ● Completion + termination (onComplete) public Observable article() {}

Slide 6

Slide 6 text

Subscribing Disposable d = article().subscribeWith(new DisposableObserver() { @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 } });

Slide 7

Slide 7 text

Subscribing Disposable d = article().subscribe(new Consumer() { @Override public void accept(Article article) { } }, new Consumer() { @Override public void accept(Throwable throwable) { } }, new Action() { @Override public void run() { } });

Slide 8

Slide 8 text

Subscription (lambda expressions) Disposable d = article().subscribe( article -> { ... }, error -> { ... }, () -> { ... });

Slide 9

Slide 9 text

No longer interested d.dispose(); ● Always unsubscribe when done! (e.g. life cycle) ● Memory leaks may occur otherwise ● Don’t “lose” subscriptions (disposables)

Slide 10

Slide 10 text

Steep learning curve?

Slide 11

Slide 11 text

Architectural change A different way requires different thinking

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

public Observable 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(); }

Slide 15

Slide 15 text

Functional style Immutable types, chaining of operations

Slide 16

Slide 16 text

Operators Lots of them!

Slide 17

Slide 17 text

Documentation Might be daunting (generics, marble diagrams)

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

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.

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Convert from Article to rfidTag string Observable newObservable = observable.map(article -> article.rfIdTag);

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Count (group) same articles public Observable quantity(Observable 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)) ); }

Slide 28

Slide 28 text

Count (group) same articles public Observable quantity(Observable 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)) ); }

Slide 29

Slide 29 text

Count (group) same articles public Observable quantity(Observable 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)) ); }

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Count (group) same articles public Observable quantity(Observable observable) { return observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable // group type: Observable> flatMap(group -> // GroupObservable // 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)) ); }

Slide 32

Slide 32 text

Count (group) same articles public Observable quantity(Observable observable) { return observable.filter(article -> article.isInStock). groupBy(this::articleIdFromTag). // convert each group to a new observable // group type: Observable> flatMap(group -> // GroupObservable // 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)) ); }

Slide 33

Slide 33 text

Count (group) same articles public Observable quantity(Observable 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)) ); }

Slide 34

Slide 34 text

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)

Slide 35

Slide 35 text

Schedulers (threading) ● Schedulers.io(), Schedulers.computation(), Schedulers.from(executor) … ● AndroidSchedulers.mainThread() from rxandroid ● subscribeOn() and observeOn() operators

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Common mistakes

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Subscriptions ● Rock it like it’s AsyncTask public Observable fetchValueFromTheNetwork() { return createObservable().subscribeOn(Schedulers.io()). observeOn(AndroidSchedulers.mainThread()); } mDisposables.add(fetchValueFromTheNetwork().subscribe(value -> { … }, error -> { … }));

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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 retrieveArticle() { return articleHttpCall(). subscribeOn(Schedulers.io()). observeOn(AndroidSchedulers.mainThread()); } // does the right thing retrieveArticle().subscribe(article -> { … }, … );

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Not every problem is a nail List articleList = getArticleList(); Observable.from(articleList).subscribe(article -> { printArticle(article); doSomethingElse(article); });

Slide 47

Slide 47 text

Conclusion ● RxJava is a different way of doing things ● Very powerful, but takes time and practice to learn ● Take small steps learning it

Slide 48

Slide 48 text

onComplete() @botteaap [email protected] questions().subscribe(question -> { repeatTheQuestion(question); answerQuestion(question); }, error -> { //TODO handle error :) }, this::coffeeBreak);

Slide 49

Slide 49 text

Resources https://github.com/ReactiveX/RxJava https://github.com/ReactiveX/Rxandroid https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0 https://bitbucket.org/littlerobots/rxlint