Slide 1

Slide 1 text

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

Slide 2

Slide 2 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 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Subscription Subscription s = article().subscribe(new Subscriber() { @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 } });

Slide 6

Slide 6 text

Subscription Subscription s = article().subscribe(new Action1() { @Override public void call(Article article) { } }, new Action1() { @Override public void call(Throwable throwable) { } }, new Action0() { @Override public void call() { } });

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Steep learning curve?

Slide 10

Slide 10 text

Architectural change A different way requires different thinking

Slide 11

Slide 11 text

Architectural change Stream of events vs a single value

Slide 12

Slide 12 text

Architectural change Does this stream ever complete?

Slide 13

Slide 13 text

Architectural change Am I handling errors correctly?

Slide 14

Slide 14 text

Functional style Immutable types, chaining of operations

Slide 15

Slide 15 text

Operators Lots of them!

Slide 16

Slide 16 text

Documentation Might be daunting (generics, marble diagrams)

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 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 19

Slide 19 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 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

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 25

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

Slide 26

Slide 26 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 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

No content

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 // 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 30

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Postpone subscription to the source until connect() is called ConnectableObservable ConnectableObservable connectable = observable.publish(); Subscription s1 = connectable.subscribe(); Subscription s2 = connectable.subscribe(); connectable.connect();

Slide 35

Slide 35 text

Connect only after a specific number of subscriptions AutoConnect ConnectableObservable connectable = observable.publish().autoConnect(2); Subscription s1 = connectable.subscribe(); Subscription s2 = connectable.subscribe(); // happens automatically now // connectable.connect();

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 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 39

Slide 39 text

Common mistakes

Slide 40

Slide 40 text

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

Slide 41

Slide 41 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 42

Slide 42 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 43

Slide 43 text

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

Slide 44

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

Slide 45

Slide 45 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 46

Slide 46 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 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

onCompleted() @botteaap [email protected]