Date post: | 17-Jan-2017 |
Category: |
Software |
Upload: | constantine-mars |
View: | 57 times |
Download: | 3 times |
What it means to be Reactive?
RxJava for Android
Constantine MarsSenior Android Developer @ DataArtGDG Dnipro and JUG Dnepr
#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
#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
#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)
#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
#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
#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.
#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
#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
#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
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
#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
#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/
Thank you!Questions?
+ConstantineMarsdataart.com.uadnipro.gdg.org.ua