Slide 1

Slide 1 text

Ron Shapiro · @rdshapiro DroidconNYC 2014 Easy wins to help get you in the groove RxJava + Android

Slide 2

Slide 2 text

Quick Survey Who has played around with RxJava? Who uses RxJava in production? compile 'io.reactivex:rxjava:1.+' formerly: compile 'com.netflix.rxjava:rxjava-core:+'

Slide 3

Slide 3 text

What is Rx? Let's take a step back. Imperative programming:

Slide 4

Slide 4 text

What is Rx? int a = 1; int b = a + 2; a = 5; assertTrue(3, b); Let's take a step back. Imperative programming:

Slide 5

Slide 5 text

A "real-world" example of Reactive Programming

Slide 6

Slide 6 text

A "real-world" example of Reactive Programming Excel spreadsheet:

Slide 7

Slide 7 text

A "real-world" example of Reactive Programming Excel spreadsheet: A 1 2 2 4 3 =A1 + A2

Slide 8

Slide 8 text

A "real-world" example of Reactive Programming Excel spreadsheet: A 1 2 2 4 3 =A1 + A2 A 1 2 2 4 3 6

Slide 9

Slide 9 text

A "real-world" example of Reactive Programming Excel spreadsheet: A 1 2 2 4 3 =A1 + A2 A 1 2 2 4 3 6 A 1 2 2 5 3 7

Slide 10

Slide 10 text

RxJava Contract void onNext(T t); void onError(Throwable throwable); void onCompleted();

Slide 11

Slide 11 text

RxJava Contract void onNext(T t); void onError(Throwable throwable); void onCompleted(); T get(); Iterable get(); vs. Imperative Programming

Slide 12

Slide 12 text

RxJava Contract void onNext(T t); void onError(Throwable throwable); void onCompleted(); T get() throws Throwable; Iterable get() throws Throwable; vs. Imperative Programming

Slide 13

Slide 13 text

In Action Observable.just(1, 2, 3, 4) .subscribe(new Subscriber() { @Override public void onNext(Integer integer) { System.out.println(integer); } @Override public void onCompleted() { System.out.println("Done!"); } ! @Override public void onError(Throwable e) { System.err.println("An error occurred: " + e.getMessage()); } }); ! > 1 > 2 > 3 > 4 > Done!

Slide 14

Slide 14 text

Java 8 to the Rescue!

Slide 15

Slide 15 text

Java 8 to the Rescue! Java 8 to the Rescue!

Slide 16

Slide 16 text

Java 8 to the Rescue! Java 8 to the Rescue! Yes, on Android!

Slide 17

Slide 17 text

Retrolambda ! https://github.com/evant/gradle-retrolambda Observable.just(1, 2, 3, 4).subscribe( integer -> System.out.println(integer), error -> System.err.println("An error occurred: " + error.getMessage()), () -> System.out.println(“Done!"));

Slide 18

Slide 18 text

Observable.just(1, 2, 3, 4).subscribe( + integer -> System.out.println(integer), - System.out::println, error -> System.err.println("An error occurred: " + error.getMessage()), () -> System.out.println("Done!")); Retrolambda ! https://github.com/evant/gradle-retrolambda

Slide 19

Slide 19 text

Observable.just(1, 2, 3, 4).subscribe( + integer -> System.out.println(integer), - System.out::println, error -> System.err.println("An error occurred: " + error.getMessage()), () -> System.out.println("Done!")); Retrolambda ! https://github.com/evant/gradle-retrolambda Not only is this more concise, Java 8 lambdas don't have an implicit reference to their enclosing block's class like anonymous classes do (unless you explicitly refer to MyClass.this)

Slide 20

Slide 20 text

So why would I actually use this? Ok.

Slide 21

Slide 21 text

Long running tasks

Slide 22

Slide 22 text

(Potentially) long running tasks

Slide 23

Slide 23 text

(Potentially) long running tasks db.loadSomeData() .subscribe(data -> textView.setText(data.toString));

Slide 24

Slide 24 text

(Potentially) long running tasks db.loadSomeData() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> textView.setText(data.toString));

Slide 25

Slide 25 text

(Potentially) long running tasks Observable loadSomeData() { // loading is un-opinionated about // what thread it is running on } db.loadSomeData() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> textView.setText(data.toString));

Slide 26

Slide 26 text

(Potentially) long running tasks Observable loadSomeData() { // loading is un-opinionated about // what thread it is running on } db.loadSomeData() .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> textView.setText(data.toString));

Slide 27

Slide 27 text

Observables + SQL

Slide 28

Slide 28 text

Observables + SQL shameless plug for github.com/venmo/cursor-utils

Slide 29

Slide 29 text

Observables + SQL shameless plug for github.com/venmo/cursor-utils IterableCursor: turn a Cursor row into a T IterableCursorAdapter adapter = new IterableCursorAdapter(context); db.loadSomeData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(adapter::changeCursor);

Slide 30

Slide 30 text

Observables + SQL Because IterableCursor extends Iterable, you can easily transform your queries into Observables:

Slide 31

Slide 31 text

Observables + SQL db.loadSomeData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(cursor -> Observable.from(cursor)) .onNext(pojo -> /* ... */); Because IterableCursor extends Iterable, you can easily transform your queries into Observables:

Slide 32

Slide 32 text

Observables + SQL Because IterableCursor extends Iterable, you can easily transform your queries into Observables: db.loadSomeData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(cursor -> Observable.from(cursor)) - .onNext(pojo -> /* ... */); + .toList() + .onNext(pojoList -> /* ... */);

Slide 33

Slide 33 text

Observables have a semi-declaritive syntax

Slide 34

Slide 34 text

Observables have a semi-declaritive syntax // detect when the user pauses typing, // then search the api ViewObservable.text(myTextView) .map(TextView::getText) .delay(400, TimeUnit.MILLISECONDS) .filter(delayedText -> TextUtils.equals( delayedText, myTextView.getText())) .subscribe(api::search);

Slide 35

Slide 35 text

Observables have a semi-declaritive syntax // detect when the user pauses typing, // then search the api ViewObservable.text(myTextView) .map(TextView::getText) + .map(CharSequence::toString()) .delay(400, TimeUnit.MILLISECONDS) .filter(delayedText -> TextUtils.equals( delayedText, myTextView.getText())) .subscribe(api::search); // make sure you're always dealing with immutable objects!

Slide 36

Slide 36 text

Form Validation

Slide 37

Slide 37 text

Form Validation Observable validName = ViewObservable .text(nameTextView) .map(view -> TextUtils.isEmpty(view.getText())); Observable validTermsOfService = ViewObservable.input(tosCheckBox, true); Observable validBirthdate = // ...

Slide 38

Slide 38 text

Form Validation

Slide 39

Slide 39 text

Form Validation Func3 combinator = (name, tos, date) -> name && tos && date); Observable.combineLatest(validName, validTermsOfService, validBirthdate, combinator) .subscribe(allValid -> { if (allValid) { submitButton.setVisibility(View.VISIBLE); } else { submitButton.setVisibility(View.GONE); } });

Slide 40

Slide 40 text

Form Validation Func3 combinator = (name, tos, date) -> name && tos && date); Observable.combineLatest(validName, validTermsOfService, validBirthdate, combinator) .subscribe(allValid -> { if (allValid) { submitButton.setVisibility(View.VISIBLE); } else { submitButton.setVisibility(View.GONE); } }); combineLatest and FuncX are type-safe! Adding a new input source in one area but not the other will break at compile-time instead of a silent runtime mistake

Slide 41

Slide 41 text

Composability

Slide 42

Slide 42 text

sp? Composability

Slide 43

Slide 43 text

API Client

Slide 44

Slide 44 text

API Client Observable getMetaData() {/* ... */} Observable postPicture(String url, Bitmap b) {/* ... */} Observable linkPostToPermalink(Strind id, Permalink p) {/* ... */}

Slide 45

Slide 45 text

API Client Observable getMetaData() {/* ... */} Observable postPicture(String url, Bitmap b) {/* ... */} Observable linkPostToPermalink(Strind id, Permalink p) {/* ... */} ! // if any of these fail, i.e. call `onError` instead of `onCompleted`, // the subsequent calls are not made getMetaData() .flatMap(metadata -> postPicture(metadata.getUrl(), bitmap) .flatMap(permalink -> linkPostToPermalink(postId, permalink) .subscribe(/* ... */);

Slide 46

Slide 46 text

API Client Observable getMetaData() {/* ... */} Observable postPicture(String url, Bitmap b) {/* ... */} Observable linkPostToPermalink(Strind id, Permalink p) {/* ... */} ! // if any of these fail, i.e. call `onError` instead of `onCompleted`, // the subsequent calls are not made getMetaData() .flatMap(metadata -> postPicture(metadata.getUrl(), bitmap) .flatMap(permalink -> linkPostToPermalink(postId, permalink) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* ... */);

Slide 47

Slide 47 text

API Client

Slide 48

Slide 48 text

API Client Observable first = // ... Observable second = // ... Observable third = // ... Func3 combineFunc = // ...

Slide 49

Slide 49 text

API Client // wait for all 3 to complete Observable.combineLatest(first, second, third, combineFunc) .subscribe((R r) -> doSomethingAwesome()); Observable first = // ... Observable second = // ... Observable third = // ... Func3 combineFunc = // ...

Slide 50

Slide 50 text

API Client // wait for all 3 to complete Observable.combineLatest(first, second, third, combineFunc) .subscribe((R r) -> doSomethingAwesome()); Observable first = // ... Observable second = // ... Observable third = // ... Func3 combineFunc = // ... Make sure each of first, second, and third handles off-loading/concurrency concerns. !

Slide 51

Slide 51 text

RxEventBus

Slide 52

Slide 52 text

RxEventBus Subject // observe a T, emit an R (can be the same) AsyncSubject: last emitted item BehaviorSubject: most recent + all future items PublishSubject: all future items ReplaySubject: caches all* items, emits each on subscribing

Slide 53

Slide 53 text

RxEventBus class RxEventBus { private Subject subject = PublishSubject.create(); Subscription subscribe(Action1 super T> action, Class clazz) { return subject.ofType(clazz).subscribe(action); } void post(T t) { subject.onNext(t); } void post(Observable observable) { observable.subscribe(subject); } }

Slide 54

Slide 54 text

RxEventBus class RxEventBus { // ... /** Forwarding terminal events will terminate your Subject! */ void post(Observable observable) { observable.materialize().map((Notification notification) -> { if (notification.isOnNext()) { return notification.getValue(); } else if (notification.isOnError()) { return new OnErrorEvent(notification.getThrowable()); } return new OnCompletedEvent(); }).subscribe(subject); } }

Slide 55

Slide 55 text

RxEventBus class RxEventBus { // ... /** Forwarding terminal events will terminate your Subject! */ void post(Observable observable) { observable.materialize().map((Notification notification) -> { if (notification.isOnNext()) { return notification.getValue(); } else if (notification.isOnError()) { return new OnErrorEvent(notification.getThrowable()); } return new OnCompletedEvent(); }).subscribe(subject); } }

Slide 56

Slide 56 text

Simplified Testing

Slide 57

Slide 57 text

Simplified Testing Observable sleepObservable(long timeout, TimeUnit unit) { return Observable.create((Subscriber super T> subscriber) -> { SystemClock.sleep(unit.toMillis(timeout)); subscriber.onCompleted(); }); }

Slide 58

Slide 58 text

Simplified Testing Observable sleepObservable(long timeout, TimeUnit unit) { return Observable.create((Subscriber super T> subscriber) -> { SystemClock.sleep(unit.toMillis(timeout)); subscriber.onCompleted(); }); } Database db = Mockito.spy(db); doAnswer(mockedMethod -> { return sleepObservable(2, SECONDS) .concatWith(mockedMethod.callRealMethod()); }).when(db).loadSomeData()

Slide 59

Slide 59 text

Simplified Testing AtomicBoolean wasClicked = new AtomicBoolean(false); new Thread(() -> { SystemClock.sleep(1000); assertTrue(wasClicked.get()); }).start(); onView(withId(R.id.my_view) .perform(setOnClickListener((v) -> wasClicked.set(true)) .perform(click(); // if your data is being loaded on the main-thread, // the view will not be clicked

Slide 60

Slide 60 text

Look familiar? UNEXPECTED TOP-LEVEL EXCEPTION: java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:276) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167) at com.android.dx.merge.DexMerger.merge(DexMerger.java:188) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287) at com.android.dx.command.dexer.Main.run(Main.java:230) at com.android.dx.command.dexer.Main.main(Main.java:199) at com.android.dx.command.Main.main(Main.java:103)

Slide 61

Slide 61 text

Look familiar? UNEXPECTED TOP-LEVEL EXCEPTION: java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:276) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167) at com.android.dx.merge.DexMerger.merge(DexMerger.java:188) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287) at com.android.dx.command.dexer.Main.run(Main.java:230) at com.android.dx.command.dexer.Main.main(Main.java:199) at com.android.dx.command.Main.main(Main.java:103) RxJava is pretty big - you may/may not need ProGuard

Slide 62

Slide 62 text

Ron Shapiro shapiro.rd@gmail.com @rdshapiro ! venmo.com/ronshapiro Questions?