GKAC 2015 Apr. - RxAndroid

83
RxAndroid Leo Y. Kim (@dalinaum)

Transcript of GKAC 2015 Apr. - RxAndroid

RxAndroidLeo Y. Kim (@dalinaum)

Rx

The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators.

https://msdn.microsoft.com/en-us/data/gg577609

RxJava

RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences.

Copyright 2013 Netflix, Inc.(https://github.com/ReactiveX/RxJava)

RxAndroid

RxAndroid

build.gradle

dependencies { ... compile 'io.reactivex:rxandroid:0.24.0'}

Observable and Subscriber

RiverObservable

onNext

onNext

onCompleted

onNext

An Observable calls this method whenever the Observable emits an item. This method takes as a parameter the item emitted by the Observable.

onError

An Observable calls this method to indicate that it has failed to generate the expected data or has encountered some other error. This stops the Observable and it will not make further calls to onNext or onCompleted. The onError method takes as its parameter an indication of what caused the error.

onCompleted

An Observable calls this method after it has called onNext for the final time, if it has not encountered any errors.

3 methods

Simple ObservableObservable<String> simpleObservable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello RxAndroid !!"); subscriber.onCompleted(); } });

Simple(?) SubscribersimpleObservable .subscribe(new Subscriber<String>() { @Override public void onCompleted() { Log.d(TAG, "complete!"); } (cont.)

Subscriber @Override public void onError(Throwable e) { Log.e(TAG, "error: " + e.getMessage()); } @Override public void onNext(String text) { ((TextView) findViewById(R.id.textView)).setText(text); } });

Is it simple subsciber?

Not yet...

simpleObservable .subscribe(new Action1<String>() { @Override public void call(String text) { ((TextView) findViewById(R.id.textView)).setText(text); } });

More simple subscriber #1

simpleObservable .subscribe(new Action1<String>() { ... }, new Action1<Throwable>() { ... }, new Action0() { ... });

More simple subscriber #2

simpleObservable .subscribe(new Action1<String>() { ... }, new Action1<Throwable>() { ... });

More simple subscriber #3

Operator: Map

Operator: Map

.map(new Func1<Integer, Integer>() { @Override public Integer call(Integer value) { return value * 10; }})

Operator: mapsimpleObservable

.map(new Func1<String, String>() {

@Override

public String call(String text) {

return text.toUpperCase();

}

})

.subscribe(new Action1<String>() {

@Override

public void call(String text) {

((TextView) findViewById(R.id.textView)).setText(text);

}

});

Operator: mapsimpleObservable

.map(new Func1<String, Integer>() {

@Override

public Integer call(String text) {

return text.length();

}

})

.subscribe(new Action1<Integer>() {

@Override

public void call(Integer length) {

((TextView) findViewById(R.id.textView)).setText("length: " + length);

}

});

More operators

http://reactivex.io/documentation/operators.html

Observer utilities

Observable<String> simpleObservable = Observable.just("Hello RxAndroid");

see also: Observable.from (for collections)

LambdaObservable<String> simpleObservable = Observable.just("Hello Lambda!!");

simpleObservable .map(text -> text.length()) .subscribe(length ->((TextView)findViewById(R.id.textView)) .setText("length: " + length));

What is that?

Java 8 Lambda

Lambda: stage 1.map(new Func1<String, Integer>() { @Override public Integer call(String text) { return text.length(); } })

Lambda: stage 2

.map((String text) -> { return text.length();})

Lambda: stage 3

.map((text) -> { return text.length();})

Lambda: stage 4

.map(text -> { return text.length();})

Lambda: stage 5

.map(text -> text.length())

Wait? Java 8!?!

Java 8 Lambda

Retrolambda

Java 8 Lambda

buildscript { repositories { jcenter() }

dependencies { classpath 'me.tatarka:gradle-retrolambda:2.5.0' }}

apply plugin: 'com.android.application'apply plugin: 'me.tatarka.retrolambda'

build.gradle

android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } … }

build.gradle

RxAndroid: ViewObservable.clicks

ViewObservable.clicksViewObservable .clicks(findViewById(R.id.button)) .map(event -> new Random().nextInt()) .subscribe(value -> { TextView textView = (TextView) findViewB yId(R.id.textView); textView.setText("number: " + value.toString()); }, throwable -> { Log.e(TAG, "Error: " + throwable.getMessage()); throwable.printStackTrace(); });

Observable<Event>ViewObservable .clicks(findViewById(R.id.button)) .map(event -> new Random().nextInt()) .subscribe(value -> { TextView textView = (TextView) findViewById(R.id.textView); textView.setText("number: " + value.toString()); }, throwable -> { Log.e(TAG, "Error: " + throwable.getMessage()); throwable.printStackTrace(); });

Map: Event -> IntegerViewObservable .clicks(findViewById(R.id.button)) .map(event -> new Random().nextInt()) .subscribe(value -> { TextView textView = (TextView) findViewById(R.id.textView); textView.setText("number: " + value.toString()); }, throwable -> { Log.e(TAG, "Error: " + throwable.getMessage()); throwable.printStackTrace(); });

SubscriberViewObservable .clicks(findViewById(R.id.button)) .map(event -> new Random().nextInt()) .subscribe(value -> { TextView textView = (TextView) findViewById(R.id.textView); textView.setText("number: " + value.toString()); }, throwable -> { Log.e(TAG, "Error: " + throwable.getMessage()); throwable.printStackTrace(); });

Observable.merge

Merge

Observable.mergeObservable<String> lefts = ViewObservable.clicks(findViewById(R.id.leftButton)) .map(event -> "left");

Observable<String> rights = ViewObservable.clicks(findViewById(R.id.rightButton)) .map(event -> "right");

Observable<String> together = Observable.merge(lefts, rights);

together.subscribe(text -> ((TextView) findViewById(R.id.textView)).setText(text));

together.map(text -> text.toUpperCase()) .subscribe(text -> Toast.makeText(this, text, Toast.LENGTH_SHORT).show());

2 ObservablesObservable<String> lefts = ViewObservable.clicks(findViewById(R.id.leftButton)) .map(event -> "left");

Observable<String> rights = ViewObservable.clicks(findViewById(R.id.rightButton)) .map(event -> "right");

Observable<String> together = Observable.merge(lefts, rights);

together.subscribe(text -> ((TextView) findViewById(R.id.textView)).setText(text));

together.map(text -> text.toUpperCase()) .subscribe(text -> Toast.makeText(this, text, Toast.LENGTH_SHORT).show());

Observable.mergeObservable<String> lefts = ViewObservable.clicks(findViewById(R.id.leftButton)) .map(event -> "left");

Observable<String> rights = ViewObservable.clicks(findViewById(R.id.rightButton)) .map(event -> "right");

Observable<String> together = Observable.merge(lefts, rights);

together.subscribe(text -> ((TextView) findViewById(R.id.textView)).setText(text));

together.map(text -> text.toUpperCase()) .subscribe(text -> Toast.makeText(this, text, Toast.LENGTH_SHORT).show());

2 SubscriberObservable<String> lefts = ViewObservable.clicks(findViewById(R.id.leftButton)) .map(event -> "left");

Observable<String> rights = ViewObservable.clicks(findViewById(R.id.rightButton)) .map(event -> "right");

Observable<String> together = Observable.merge(lefts, rights);

together.subscribe(text -> ((TextView) findViewById(R.id.textView)).setText(text));

together.map(text -> text.toUpperCase()) .subscribe(text -> Toast.makeText(this, text, Toast.LENGTH_SHORT).show());

Operator: scan

Operator: scanObservable<Integer> minuses = ViewObservable.clicks(findViewById(R.id.minusButton)) .map(event -> -1);Observable<Integer> pluses = ViewObservable.clicks(findViewById(R.id.plusButton)) .map(event -> 1);

Observable<Integer> together = Observable.merge(minuses, pluses);

together.scan(0, (sum, number) -> sum + 1) .subscribe(count -> ((TextView) findViewById(R.id.count)).setText(count.toString()));together.scan(0, (sum, number) -> sum + number) .subscribe(number -> ((TextView) findViewById(R.id.number)).setText(number.toString()));

2 Observables (+map)Observable<Integer> minuses = ViewObservable.clicks(findViewById(R.id.minusButton)) .map(event -> -1);Observable<Integer> pluses = ViewObservable.clicks(findViewById(R.id.plusButton)) .map(event -> 1);

Observable<Integer> together = Observable.merge(minuses, pluses);

together.scan(0, (sum, number) -> sum + 1) .subscribe(count -> ((TextView) findViewById(R.id.count)).setText(count.toString()));together.scan(0, (sum, number) -> sum + number) .subscribe(number -> ((TextView) findViewById(R.id.number)).setText(number.toString()));

MergeObservable<Integer> minuses = ViewObservable.clicks(findViewById(R.id.minusButton)) .map(event -> -1);Observable<Integer> pluses = ViewObservable.clicks(findViewById(R.id.plusButton)) .map(event -> 1);

Observable<Integer> together = Observable.merge(minuses, pluses);

together.scan(0, (sum, number) -> sum + 1) .subscribe(count -> ((TextView) findViewById(R.id.count)).setText(count.toString()));together.scan(0, (sum, number) -> sum + number) .subscribe(number -> ((TextView) findViewById(R.id.number)).setText(number.toString()));

1st scan + subscriber: counterObservable<Integer> minuses = ViewObservable.clicks(findViewById(R.id.minusButton)) .map(event -> -1);Observable<Integer> pluses = ViewObservable.clicks(findViewById(R.id.plusButton)) .map(event -> 1);

Observable<Integer> together = Observable.merge(minuses, pluses);

together.scan(0, (sum, number) -> sum + 1) .subscribe(count -> ((TextView) findViewById(R.id.count)).setText(count.toString()));together.scan(0, (sum, number) -> sum + number) .subscribe(number -> ((TextView) findViewById(R.id.number)).setText(number.toString()));

2nd scan + subscriber: sumObservable<Integer> minuses = ViewObservable.clicks(findViewById(R.id.minusButton)) .map(event -> -1);Observable<Integer> pluses = ViewObservable.clicks(findViewById(R.id.plusButton)) .map(event -> 1);

Observable<Integer> together = Observable.merge(minuses, pluses);

together.scan(0, (sum, number) -> sum + 1) .subscribe(count -> ((TextView) findViewById(R.id.count)).setText(count.toString()));together.scan(0, (sum, number) -> sum + number) .subscribe(number -> ((TextView) findViewById(R.id.number)).setText(number.toString()));

Scheduler

Scheduler

Observable.from("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* an Observer */);

Observable runs on “newThread”

Observable.from("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* an Observer */);

Subscriber runs on “mainThread”

Observable.from("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* an Observer */);

Notice:Subscriber doesn’t run on mainThread immediately.

Boundnew Thread(() -> { final Handler handler = new Handler(); Observable.from("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.handlerThread(handler)) .subscribe(/* an Observer */)}, "custom-thread-1").start();

Bound to this threadnew Thread(() -> { final Handler handler = new Handler(); Observable.from("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.handlerThread(handler)) .subscribe(/* an Observer */)}, "custom-thread-1").start();

Varieties of Scheduler (RxJava)

● Schedulers.computation( )● Schedulers.from(executor)● Schedulers.immediate( )● Schedulers.io( )● Schedulers.newThread( )● Schedulers.trampoline( )

Varieties of Scheduler (RxAndroid)

● AndroidSchedulers.mainThread()● AndroidSchedulers.handlerThread(handler)

Use Schedulers without Observableworker = Schedulers.newThread().createWorker();

worker.schedule(() -> { yourWork();});

Unsubscirbe SubscriptionSubscription s = Observable.from("one", "two", "three", "four", "five").subscribe(/* Observer */)

s.unsubscribe()

Unsubscirbe CompositeSubscriptionprivate void doSomething() { mCompositeSubscription.add( s.subscirbe(/* observer*/);}@Overrideprotected void onDestroy() { super.onDestroy(); mCompositeSubscription.unsubscribe();}

Unsubscirbe Subscription@Overrideprotected void onDestroy() { super.onDestroy(); s.unsubscribe();}

Recursive Schedulersworker = Schedulers.newThread().createWorker();worker.schedule(() { yourWork(); worker.schedule(this);});

// It will stop your workerworker.unsubscribe();

Retrofit with RxAndroid

Retrofit without RxAndroid

@GET(“/user/{id}”)void getUser( @path(“id”) long id, Callback<User> callback);

Retrofit with RxAndroid

@GET(“/user/{id}”)Observable<User> getUser( @path(“id”) long id);

You can combie Retrofit’s Observables

Observable.zip( service.getUserPhoto(id), service.getPhotoMetadata(id), (photo, metadata) -> createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData));

Battery with RxAndroid

https://github.com/spoqa/battery

Battery with RxAndroid@RpcObject(uri="http://ip.jsontest.com/")public class JsonTest { @Response public String ip;}

Observable<JsonTest> jsonTestObservable = Rpc.invokeRx(new AndroidExecutionContext(this), jsonTest);

jsonTestObservable.subscribe(jsonText -> { Log.d("MAIN", jsonTest.ip);});

Subject

Subject

A Subject is a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable.

PublishSubjectPublishSubject publishSubject<View> = PublishSubject.create();view.setOnClickListener(view -> publishSubject.onNext(view));

Observable<View> observable = publishSubject.asObservable();

Use ReplaySubject for cachingReplaySubject<ImageSearchResult> mSubject = ReplaySubject.create();mSubject = ReplaySubject.create();mPageNumber = 1;

mApi.searchImage( "json", API_KEY, query, ITEM_COUNT, mPageNumber) .observeOn(AndroidSchedulers.mainThread()) .subscribe(mSubject::onNext);

Q&A