RxJava for Android - GDG DevFest Ukraine 2015

68
What it means to be Reactive? RxJava for Android Constantine Mars Senior Android Developer @ DataArt GDG Dnipro and JUG Dnepr

Transcript of RxJava for Android - GDG DevFest Ukraine 2015

What it means to be Reactive?

RxJava for Android

Constantine MarsSenior Android Developer @ DataArtGDG Dnipro and JUG Dnepr

–Ivan Morgillo, Alter Ego solutions, speech on DroidCon Berlin’2015

“A piece of cake, you know”

#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

FundamentalsThe basic concepts behind Rx

#dfua

Time to meditate...

Everything is a stream…

#dfua

The one who is listening is ObserverAnd the other one, who is emitting events, is Subject, or Observable

*Illustration from O’Reilly® HeadFirst “Design Patterns” book:

#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

#dfua

We will use it throughout presentation

Lambda syntax

t.setOnClickListener(v -> Timber.d("something"));

.JAVA

RxJava. Observable & ObserverBasics

#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

#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

#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

#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

#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 throughsubscription.

#dfua

It’s the bunch of Reactive callbacks that should be registered throughsubscription. 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

#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

#dfua

To subscribe. This means creating Subscription.

Subscription

Subscription subscription = observable.subscribe(observer);

...

if(!subscription.isUnsubscribed()) {

subscription.unsubscribe();

}

.JAVA

#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

#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

#dfua

Reactive Systems are:

*Check ReactiveManifesto.org for detailed definitions:

Message Driven

Responsive

Resilient

Scalable (Elastic)

From everything, ...even from air :)

Creating Observables

#dfua

Observable.from()

ENTER FILENAME/LANGArrayList<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

#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

#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

#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

#dfua

RxBindings

Button button;

...

RxView.clicks(button) .map(v -> ++counter) .debounce(500, TimeUnit.MILLISECONDS) .subscribe(c -> display.show("button " + c));

.JAVA

Filtering, conditions

RxJava Goods

#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.

#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.

#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.

#dfua

singleOrDefault

rx.Observable.range(0, 0)

.singleOrDefault(-1) .forEach(i -> getDisplay().show("single:" + i));

.JAVA

Check whether rx.Observable contains only one event/item.

#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

#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

#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()

#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.

#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

#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 :)

#dfua

debouncelong[] 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

#dfua

samplelong[] 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

Merge and combine

RxJava Goods

#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

#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.

RxJava Error Handling

#dfua

retryrx.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

#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

#dfua

retry

In case of error we can check condition in retry() and then

re-subscribe and try once more

Math and aggregate

RxJava Goods

#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

#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

#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

Ubiquitous mediums that like actors can play any role

RxJava Subjects

#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.

#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

#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

#dfua

ReactiveX Diagrams

In a hour of despair - seek inspiration at ReactiveX.io

RxJava Schedulers

#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

#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

#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...

#dfua

Android UIMathObservable.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

Comes to play

RxAndroid

#dfua

Relationship to RxJava

#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

#dfua

Activity Lifecycle

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

RxJavaJourney comes to end

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

#dfua

ReactiveX RxJava Additional Reading