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

[Constantine Mars] RxJava for Android

[Constantine Mars] RxJava for Android

Presentation from GDG DevFest Ukraine 2015 - the biggest Google related event in the country. October 23-24, Lviv. Learn more at http://devfest.gdg.org.ua/

Google Developers Group Lviv

October 23, 2015
Tweet

More Decks by Google Developers Group Lviv

Other Decks in Programming

Transcript

  1. What it means to be Reactive? RxJava for Android Constantine

    Mars Senior Android Developer @ DataArt GDG Dnipro and JUG Dnepr
  2. #dfua Faces of the Reactive Programming World Ben Christensen, Netflix

    - RxJava Erik Meijer, Applied Duality - Rx.NET Jake Wharton, Square - RxAndroid Ivan Morgillo, Alter Ego - first book about RxAndroid
  3. #dfua The one who is listening is Observer And the

    other one, who is emitting events, is Subject, or Observable *Illustration from O’Reilly® HeadFirst “Design Patterns” book:
  4. #dfua This is well known interface in for both Android

    and Desktop developers Observer = Listener t.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Timber.d("something"); } }); .JAVA
  5. #dfua It’s the source of events Observable rx.Observable<Integer> observable =

    rx.Observable.create( new rx.Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = 0; i < N; i++) { Integer integer = random.nextInt(MAX); subscriber.onNext(integer); } subscriber.onCompleted(); } }); .JAVA Be aware: there is java.util.Observable besides rx.Observable - they’re not the same
  6. #dfua It’s the source of events. It handles subscriptions through

    OnSubscribe callback Observable rx.Observable<Integer> observable = rx.Observable.create( new rx.Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = 0; i < N; i++) { Integer integer = random.nextInt(MAX); subscriber.onNext(integer); } subscriber.onCompleted(); } }); .JAVA Be aware: there is java.util.Observable besides rx.Observable - they’re not the same
  7. #dfua It’s the source of events. It handles subscriptions through

    OnSubscribe callback. When observer subscribes, it is named subscriber inside of a call() Observable rx.Observable<Integer> observable = rx.Observable.create( new rx.Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = 0; i < N; i++) { Integer integer = random.nextInt(MAX); subscriber.onNext(integer); } subscriber.onCompleted(); } }); .JAVA Be aware: there is java.util.Observable besides rx.Observable - they’re not the same
  8. #dfua It’s the source of events. It handles subscriptions through

    OnSubscribe callback. When observer subscribes, it is named subscriber inside of a call() Observable rx.Observable<Integer> observable = rx.Observable.create( new rx.Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = 0; i < N; i++) { Integer integer = random.nextInt(MAX); subscriber.onNext(integer); } subscriber.onCompleted(); } }); .JAVA Be aware: there is java.util.Observable besides rx.Observable - they’re not the same
  9. #dfua Observer rx.Observer<Integer> observer = new rx.Observer<Integer>() { @Override public

    void onCompleted() { display.show("completed"); } @Override public void onError(Throwable e) { display.show("error: " + e.getMessage()); } @Override public void onNext(Integer integer) { display.show("next: " + integer); } }; observable.subscribe(observer); .JAVA It’s the bunch of Reactive callbacks that should be registered through subscription.
  10. #dfua It’s the bunch of Reactive callbacks that should be

    registered through subscription. And handles incoming onNext(), onCompleted() and onError() from Observable Observer rx.Observer<Integer> observer = new rx.Observer<Integer>() { @Override public void onCompleted() { display.show("completed"); } @Override public void onError(Throwable e) { display.show("error: " + e.getMessage()); } @Override public void onNext(Integer integer) { display.show("next: " + integer); } }; observable.subscribe(observer); .JAVA
  11. #dfua But to make magic we need one more thing...

    Observer rx.Observer<Integer> observer = new rx.Observer<Integer>() { @Override public void onCompleted() { display.show("completed"); } @Override public void onError(Throwable e) { display.show("error: " + e.getMessage()); } @Override public void onNext(Integer integer) { display.show("next: " + integer); } }; observable.subscribe(observer); .JAVA
  12. #dfua To subscribe. This means creating Subscription. Subscription Subscription subscription

    = observable.subscribe(observer); ... if(!subscription.isUnsubscribed()) { subscription.unsubscribe(); } .JAVA
  13. #dfua To subscribe. This means creating Subscription. In fact Subscription

    class is rarely used, but can be useful to unsubscribe when we don’t need to receive events Subscription Subscription subscription = observable.subscribe(observer); ... if(!subscription.isUnsubscribed()) { subscription.unsubscribe(); } .JAVA
  14. #dfua Looks more readable, isn’t it? At least takes one

    screen, not three :) Observable + Observer with lambdas rx.Observable<Integer> observable = rx.Observable.create(subscriber -> { for (int i = 0; i < N; i++) subscriber.onNext(random.nextInt(MAX)); subscriber.onCompleted(); }); observable.subscribe( integer -> display.show("next: " + integer), throwable -> display.show("error: " + throwable.getMessage()), () -> display.show("completed")); .JAVA
  15. #dfua Observable.from() ENTER FILENAME/LANG ArrayList<Integer> arrayList = new ArrayList<>(); int

    MAX_N = random.nextInt(12) + 5; for (int i = 0; i < MAX_N; i++) arrayList.add(random.nextInt(MAX)); rx.Observable.from(arrayList) .subscribe( integer -> display.show("next: " + integer), throwable -> display.show("error: " + throwable.getMessage()), () -> display.show("complete")); .JAVA
  16. #dfua Observable.just() ENTER FILENAME/LANG private List<Integer> generate() { Random r

    = new Random(); int n = r.nextInt(5) + 5; ArrayList<Integer> a = new ArrayList<>(); for (int i = 0; i < n; i++) a.add(r.nextInt(100)); return a; } public void just() { rx.Observable.just(generate()) .subscribe(integer -> display.show("next: " + integer), throwable -> display.show("error: " + throwable.getMessage()), () -> display.show("complete")); } .JAVA
  17. #dfua Observable.interval() Random r = new Random(); rx.Observable.interval(2, TimeUnit.SECONDS) .map(t

    -> new long[]{t, r.nextInt(100)}) .limit(5) .subscribe( tuple -> display.show("next: " + Arrays.toString(tuple)), throwable -> { }, () -> display.show("complete")); .JAVA
  18. #dfua Retrofit RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.github.com") .build(); GitHubService

    service = restAdapter.create(GitHubService.class); // Retrofit can return observable which we handle as any other observable service.listRepos("c-mars") .flatMap(Observable::from) .limit(10) .subscribe(repo -> display.show("next: " + repo.toString()), throwable -> display.show("error: " + throwable.getMessage()), () -> display.show("completed")); .JAVA
  19. #dfua RxBindings Button button; ... RxView.clicks(button) .map(v -> ++counter) .debounce(500,

    TimeUnit.MILLISECONDS) .subscribe(c -> display.show("button " + c)); .JAVA
  20. #dfua Blocking private final AmmeterReadings[] data = { new AmmeterReadings(1,

    0.5), ... }; private static float getMaxValue(AmmeterReadings[] data) { return MathObservable.max(rx.Observable.from(data) .map(AmmeterReadings::getCurrent)) .toBlocking().firstOrDefault(0L); } .JAVA By default rx.Observable is async. But it can be converted to BlockingObservable and return result in-place, using functional computations.
  21. #dfua first, last, take, orDefault private final AmmeterReadings[] data =

    { new AmmeterReadings(1, 0.5), ... }; private static float getMaxValue(AmmeterReadings[] data) { return MathObservable.max(rx.Observable.from(data) .map(AmmeterReadings::getCurrent)) .toBlocking().firstOrDefault(0L); } .JAVA We can take first, last or any item from BlockingObservable. If it’s empty, we can define default value.
  22. #dfua take from Observable private final AmmeterReadings[] data = {

    new AmmeterReadings(1, 0.5), ... }; private static float getMaxValue(AmmeterReadings[] data) { return MathObservable.max( rx.Observable.from(data) .map(AmmeterReadings::getCurrent) .takeLast(5) ) .toBlocking().firstOrDefault(0L); } .JAVA rx.Observable (non-blocking) provides method take() to take multiple items from stream.
  23. #dfua defaultIfEmpty rx.Observable.range(0, max) .defaultIfEmpty(999) .forEach(i -> getDisplay().show("range 0->" +

    max + ", value (999 if empty):" + String.valueOf(i))); .JAVA Almost the same as singleOrDefault
  24. #dfua toIterable final Integer[] data = {200, 4, 145, -1,

    10, -12, 80}; Iterable<Integer> iterable = rx.Observable.from(data) .toBlocking().toIterable(); for (Integer i : iterable) { display.show("iterable:" + i.toString()); } .JAVA BlockingObservable.toIterable() converts rx.Observable to collection
  25. #dfua forEach final Integer[] data = {200, 4, 145, -1,

    10, -12, 80}; rx.Observable.from(data) .forEach(i -> display.show("iterable:" + i.toString())); .JAVA forEach() is just shorcut to .subscribe()
  26. #dfua takeUntil rx.Observable.range(0, 10) .takeUntil(i -> i == 5) .forEach(i

    -> getDisplay().show(String.valueOf(i))); // out: 0, 1, 2, 3, 4, 5 .JAVA Just a variant of take(), which completes when condition matches.
  27. #dfua contains rx.Observable.range(0, max) .contains(2) .forEach(i -> getDisplay().show("range: " +

    0 + "->" + max + ",contains 2: " + String.valueOf(i))); // out: true (or false) .JAVA Check whether stream contains certain value
  28. #dfua filter rx.Observable.range(0, 10) .filter(i -> i > 5 &&

    i < 9 ) .forEach(i -> getDisplay().show(i)); // out: 6, 7, 8 .JAVA Like takeUntil, just filter :)
  29. #dfua debounce long[] times = {3, 2, 1, 5, 2,

    6}; rx.Observable<Pair<Integer, Long>> observable = rx.Observable.create(subscriber -> { int sz = times.length; for (int i = 0; i < sz; i++) { try { long t = times[i]; TimeUnit.MILLISECONDS.sleep(t); subscriber.onNext(new Pair<>(i, t)); } catch (InterruptedException e) { subscriber.onError(e); } } subscriber.onCompleted(); }); observable.debounce(4, TimeUnit.MILLISECONDS) .subscribe(pair -> getDisplay().show("out: value=" + pair.first + ", time=" + pair.second)); .JAVA
  30. #dfua sample long[] times = {3, 2, 1, 5, 4,

    3, 1}; rx.Observable<Pair<Integer, Long>> observable = rx.Observable.create(subscriber -> { int sz = times.length; for (int i = 0; i < sz; i++) { try { long t = times[i]; TimeUnit.MILLISECONDS.sleep(t * 10); subscriber.onNext(new Pair<>(i, t)); } catch (InterruptedException e) { subscriber.onError(e); } } subscriber.onCompleted(); }); observable.sample(40, TimeUnit.MILLISECONDS) .subscribe(pair -> getDisplay().show("out: value=" + pair.first + "; time=" + pair.second)); .JAVA
  31. #dfua merge rx.Observable first = Observable.range(0, 5); //int[] rx.Observable second

    = Observable.from(new String[]{"one", "two", "three", "four", "five"}); //String[] rx.Observable.merge(first, second) .forEach(item -> getDisplay().show(item.toString())); .JAVA Merges items from separate rx.Observables to single stream
  32. #dfua zip rx.Observable<Integer> first = Observable.range(0, 5); //int[] rx.Observable<String> second

    = Observable.from(new String[]{"one", "two", "three", "four", "five"}); //String[] rx.Observable.zip(first, second, (i, s) -> new Pair(s, i)) .forEach(pair -> getDisplay().show(pair.toString())); .JAVA Merges items from separate rx.Observables to single stream using combining function.
  33. #dfua retry rx.Observable<Integer> canFail = rx.Observable.create(new Observable.OnSubscribe<Integer>() { @Override public

    void call(Subscriber<? super Integer> subscriber) { for (int i = 0; i < 6; i++) { switch (i) { case 3: if (!failedOnce) { failedOnce = true; subscriber.onError(new Error()); return; } break; case 5: subscriber.onError(new Throwable()); return; } subscriber.onNext(i); } subscriber.onCompleted(); } }); .JAVA
  34. #dfua retry canFail.retry((integer, throwable) -> { boolean retry = (throwable

    instanceof Error); getDisplay().show("retry, errors: " + integer); return retry; }) .subscribe(i -> { getDisplay().show(i); }, throwable -> { getDisplay().show("error: " + throwable.getMessage()); }); .JAVA
  35. #dfua retry In case of error we can check condition

    in retry() and then re- subscribe and try once more
  36. #dfua MathObservable private final AmmeterReadings[] data = { new AmmeterReadings(1,

    0.5), ... }; private static float getMaxValue(AmmeterReadings[] data) { return MathObservable.max(rx.Observable.from(data) .map(AmmeterReadings::getCurrent)) .toBlocking().firstOrDefault(0L); } .JAVA Plugin MathObservable: compile 'io.reactivex:rxjava-math:1.0.0' max, average, sum, count
  37. #dfua Average rx.Observable<Integer> integers = rx.Observable.range(0, 10); MathObservable.averageInteger(integers).subscribe(avg -> {

    getDisplay().show(avg); }); .JAVA Many methods of MathObservable has type-aware alternatives
  38. #dfua reduce rx.Observable.range(0, 10).reduce((a, b) -> { int c =

    a + b; getDisplay().show("reduce: a=" + a + " + " + b + " = " + c); return c; }).forEach(value -> getDisplay().show("result: " + value)); .JAVA Classic reduce operation, common for all functional programming languages
  39. #dfua Creating Subjects PublishSubject<String> subject = PublishSubject.create(); example(subject); … ReplaySubject<String>

    subject = ReplaySubject.createWithSize(2); example(subject); .JAVA Subjects have method .create() for this. ReplaySubject can also be created with predefined number of events to replay on subscription.
  40. #dfua Example code private void example(Subject<String, String> subject) { subject.onNext("before

    1"); subject.onNext("before 2"); subject.onNext("before 3"); subject.onNext("before 4"); subject.subscribe(s -> getDisplay().show("subscribed: " + s)); subject.onNext("after 5"); subject.onNext("after 6"); subject.onNext("after 7"); subject.onNext("after 8"); subject.onCompleted(); } .JAVA Subject can act both like Observable and Observer. So we can call .onNext, . onComplete manually and trigger subscription callbacks
  41. #dfua Subjects behaviour PublishSubject Is just a proxy for events

    AsyncSubject Replays only last event onComplete ReplaySubject Replays last N events and then proxies the same as Publish BehaviorSubject Replays only last event and then proxies the same as Publish
  42. #dfua Schedulers rx.Observable.from(readFromFile(context)) .subscribeOn(Schedulers.io()) .forEach(line -> textView.append("\n" + line)); .JAVA

    By default rx.Observable is single-threaded. Here come Schedulers to hide threading and synchronization behind the functional interface. Just call .subscribeOn() and define which kind of threading you want
  43. #dfua Schedulers rx.Observable source = rx.Observable.range(0, 10).map(integer -> { List<Integer>

    outs = new ArrayList<>(); for (int i = 0; i < 1000; i++) { for (int j = 0; j < 1000; j++) { outs.add((int) Math.pow(i, j)); } } return outs; }).flatMap(Observable::from); MathObservable.sumInteger(source) .subscribeOn(Schedulers.computation()) .subscribe(integer -> textView.setText("final sum: " + integer.toString())); .JAVA
  44. #dfua Android UI MathObservable.sumInteger(source) .subscribeOn(Schedulers.computation()) .subscribe(integer -> textView.setText("final sum: "

    + integer.toString())); .JAVA Scheduler move execution to another appropriate thread. But when we try to update UI from this chain - something bad happens...
  45. #dfua Android UI MathObservable.sumInteger(source) .subscribeOn(Schedulers.computation()) .subscribe(integer -> textView.setText("final sum: "

    + integer.toString())); .JAVA Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. E/AndroidRuntime: at android.view.ViewRootImpl.checkThread(ViewRootImpl.java: 6556) E/AndroidRuntime: at android.view.ViewRootImpl.invalidateChildInParent (ViewRootImpl.java:942) E/AndroidRuntime: at android.view.ViewGroup.invalidateChild(ViewGroup.java:5081) E/AndroidRuntime: at android.view.View.invalidateInternal(View.java:12713) E/AndroidRuntime: at android.view.View.invalidate(View.java:12677) E/AndroidRuntime: at android.view.View.invalidate(View.java:12661) ... CONSOLE
  46. #dfua AndroidSchedulers rx.Observable.from(readFromFile(context)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .forEach(line -> textView.append("\n" + line));

    .JAVA .subscribeOn defines thread on which computations run, .observeOn defines thread on which rx.Observer callbacks will run
  47. #dfua Activity Lifecycle // Activity private Subscription subscription; protected void

    onCreate(Bundle savedInstanceState) { this.subscription = observable.subscribe(this); } ... protected void onDestroy() { this.subscription.unsubscribe(); super.onDestroy(); } .JAVA Use subscription or CompositeSubscription
  48. #dfua Where to look? • JavaDoc: http://reactivex.io/RxJava/javadoc/rx/Observable.html • List of

    Additional Reading from RxJava Wiki: https://github. com/ReactiveX/RxJava/wiki/Additional-Reading • RxJava Essentials by Ivan Morgillo: https://www.packtpub. com/application-development/rxjava-essentials • RxMarbles - interactive diagrams: http://rxmarbles.com/