reactive programming on android

Post on 16-Jan-2017

158 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Reactive programming on AndroidTomáš Kypta

What’s RxJava

What’s RxJava

▪ combination of

– observer pattern

– iterator pattern

– functional programming

▪ composable data flow

▪ push concept

Key Parts

▪ Observable

▪ Observer, Subscriber

▪ Subject

Key Methods

▪ onNext(T)

▪ onCompleted()

▪ onError(Throwable)

Key Methods

▪ subscribers can be replaced with Action parameters

someObservable.subscribe( onNextAction, onErrorAction, onCompleteAction);

Key Methods

▪ we often ignore onError() and onComplete()

▪ risk of crashes when error occurs

someObservable.subscribe( onNextAction);

What is RxJava good for?

▪ Async processing

– AsyncTask

– AsyncTaskLoader

– …

What is RxJava good for?

▪ Async composition

– nested API calls

– AsyncTask callback hell

– There’s no way to do it right!

What is RxJava good for?

▪ Testability

Pros & Cons

▪ powerful tool

▪ conceptually difficult

– there are some tricky parts

RxJava & app architecture

▪ suitable both for MVP and MVVM

Example

Observable.just("Hello, world!") .subscribe(new Action1<String>() { @Override public void call(String s) { Timber.d(s); } });

ExampleObservable.just("Hello, world!") .subscribe(new Subscriber<String>() { @Override public void onCompleted() {…}

@Override public void onError(Throwable e) {…}

@Override public void onNext(String s) { Timber.d(s); } });

Retrolambda

▪ Enables Java 8 lambdas on Android

Tip #1 - use Retrolambda

Observable.just("Hello, world!") .subscribe(s -> Timber.d(s));

Observable.just("Hello, world!") .subscribe(new Action1<String>() { @Override public void call(String s) { Timber.d(s); } });

Observer vs. Subscriber

?

myObservable .subscribe(new Observer<String>() { @Override public void onCompleted() {}

@Override public void onError(Throwable e) {}

@Override public void onNext(String s) {} });

myObservable .subscribe(new Subscriber<String>() { @Override public void onCompleted() {}

@Override public void onError(Throwable e) {}

@Override public void onNext(String s) {} });

Observer vs. Subscriber

▪ simpler unsubscribe() with Subscriber

myObservable .subscribe(new Subscriber<String>() { @Override public void onCompleted() { unsubscribe(); }

@Override public void onError(Throwable e) {}

@Override public void onNext(String s) {} });

Operators

Operators

▪ creation

▪ filtering

▪ combining

▪ transforming

▪ …

Understanding operators

▪ marble diagrams

▪ RxMarbles

– rxmarbles.com

– Android app

Observable Creation

▪ create()

▪ from()

▪ just()

Observable Creation

▪ corner cases

▪ empty()

▪ never()

▪ error()

Filtering

▪ filter()

▪ takeLast()

▪ take()

Filtering

▪ first(), take(int)

▪ skip()

▪ distinct()

▪ …

Combining

▪ merge()

▪ zip()

Transforming

▪ map()

▪ flatMap()

▪ concatMap()

map vs. flatMap vs. concatMap▪ map

– A -> B

– one in, one out

▪ flatMap

– A -> Observable<B>

– map + flatten

▪ concatMap

– ordered flatMap

Aggregate

▪ concat()

▪ reduce

Operators

▪ and there’s much more …

Example

Observable.from(myUrls)

Example

Observable.from(myUrls) .filter(url -> url.endsWith(".cz"))

Example

Observable.from(myUrls) .filter(url -> url.endsWith(".cz")) .flatMap(url -> Observable.just(getHttpStatus(url)))

Example

Observable.from(myUrls) .filter(url -> url.endsWith(".cz")) .flatMap(url -> Observable.just(getHttpStatus(url))) .take(5)

Example

Observable.from(myUrls) .filter(url -> url.endsWith(".cz")) .flatMap(url -> Observable.just(getHttpStatus(url))) .take(5) .subscribe(s -> Timber.i(String.valueOf(s)));

Threading

Threading

▪ subscribeOn(Scheduler)

▪ subscribes to Observable on the Scheduler

▪ observeOn(Scheduler)

▪ Observable emits on the Scheduler

Schedulers

Schedulers

▪ computation()

▪ io()

▪ newThread()

▪ from(Executor)

computation() vs. io()

▪ io()

– unbounded thread pool

– I/O operations

▪ computation()

– bounded thread pool

– CPU intensive computational work

AndroidSchedulers

▪ mainThread()

▪ from(Looper)

subscribeOn()

▪ subscribeOn()

– multiple calls useless

– only the first call works!

– for all operators

observeOn()

▪ can be called multiple times

Threading

▪ operators have default Schedulers

▪ check Javadoc for it

Threading

▪ range()

– no preference, uses current thread

▪ interval()

– computation() thread

Threading

▪ just()

– current thread

▪ delay(long, TimeUnit)

– computation() thread

Observable methods

▪ extra actions

▪ doOnNext()

▪ doOnError()

▪ doOnSubscribe()

▪ doOnUnsubscribe()

▪ …

Async composition

apiEndpoint.login()

Async composition

apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken))

Async composition

apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser())

Async composition

apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser()) .flatMap(user -> serviceEndpoint.getUserContact(user.getId()))

Subscription

Subscription

Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() );

Subscription

Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() );

s.unsubscribe();

Subscription

Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() );

s.unsubscribe();Timber.i("unsubscribed: " + s.isUnsubscribed());

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

compositeSubscription.unsubscribe();

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

compositeSubscription.unsubscribe();

compositeSubscription.clear();

Bridging non-Rx APIs

Bridging non-Rx APIs

▪ just()

private Object getData() {...}

public Observable<Object> getObservable() { return Observable.just(getData());}

Tip #2 - defer()

▪ use defer() to avoid immediate execution

private Object getData() {...}

public Observable<Object> getObservable() { return Observable.defer( () -> Observable.just(getData()) );}

Subjects

Subjects

▪ Observable & Observer

▪ bridge between non-Rx API

▪ stateful

– terminal state

▪ don’t pass data after onComplete()/onError()

Subjects

▪ cannot be reused

– always create a new instance when subscribing to an Observable

Subjects

▪ AsyncSubject

▪ BehaviorSubject

▪ ReplaySubject

▪ PublishSubject

▪ SerializedSubject

Subjects

Subject subject = …subject.subscribe(subscriber);subject.onNext(A);subject.onNext(B);

AsyncSubject

▪ last item, after Observable completes

BehaviorSubject

▪ emits most recent and all subsequent items

PublishSubject

▪ emits only subsequent items

ReplaySubject

▪ emit all the items

RxRelay

RxRelay

▪ https://github.com/JakeWharton/RxRelay

▪ Relay = Observable & Action1

▪ call()

▪ Relay = Subject - onComplete() - onError()

RxRelay

▪ Subject

▪ stateful

▪ Relay

▪ stateless

RxRelay

Relay relay = …relay.subscribe(observer);relay.call(A);relay.call(B);

RxRelay

▪ BehaviorRelay

▪ PublishRelay

▪ ReplayRelay

▪ SerializedRelay

Android Lifecycle

▪ problems

▪ continuing Subscription during configuration change

▪ memory leaks

Android Lifecycle

▪ continuing Subscription during configuration change

▪ cache()

▪ replay()

Android Lifecycle

▪ memory leaks

▪ bind to Activity/Fragment lifecycle

▪ RxLifecycle

RxLifecycle

RxLifecycle

▪ auto unsubscribe based on Activity/Fragment lifecyclemyObservable .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY)) .subscribe();

myObservable .compose(RxLifecycle.bindActivity(lifecycle)) .subscribe();

RxLifecycle

▪ obtain ActivityEvent or FragmentEvent by:

A. rxlifecycle-components + subclass RxActivity, RxFragment

B. Navi + rxlifecycle-navi

C. Write it yourself

RxLifecycle

public class MyActivity extends RxActivity { @Override public void onResume() { super.onResume(); myObservable .compose(bindToLifecycle()) .subscribe(); }}

RxLifecyclepublic class MyActivity extends NaviActivity { private final ActivityLifecycleProvider provider = NaviLifecycle.createActivityLifecycleProvider(this);

@Override public void onResume() { super.onResume(); myObservable .compose(provider.bindToLifecycle()) .subscribe(…); }}

Navi

naviComponent.addListener(Event.CREATE, new Listener<Bundle>() { @Override public void call(Bundle bundle) { setContentView(R.layout.main); } });

RxNavi

RxNavi.observe(naviComponent, Event.CREATE) .subscribe(bundle -> setContentView(R.layout.main));

RxLifecyclepublic class MainActivityFragment extends Fragment {

BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();

RxLifecyclepublic class MainActivityFragment extends Fragment {

BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();

@Override public void onResume() { super.onResume(); RxView.clicks(vBtn) .compose(RxLifecycle.bindUntilEvent(mLifecycleSubject, FragmentEvent.PAUSE)) .doOnUnsubscribe(() -> Timber.i("onUnsubscribe")) .subscribe(…); }

}

RxLifecyclepublic class MainActivityFragment extends Fragment {

BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();

@Override public void onResume() { super.onResume(); RxView.clicks(vBtn) .compose(RxLifecycle.bindUntilEvent(mLifecycleSubject, FragmentEvent.PAUSE)) .doOnUnsubscribe(() -> Timber.i("onUnsubscribe")) .subscribe(…); }

@Override public void onPause() { super.onPause(); Timber.i("onPause"); mLifecycleSubject.onNext(FragmentEvent.PAUSE); }}

RxBinding

RxBinding

▪ binding for UI widgets

RxBinding

RxView.clicks(vBtnSearch) .subscribe( v -> { Intent intent = new Intent(getActivity(), SearchActivity.class); startActivity(intent); } );

RxBinding

RxTextView.textChanges(vEtSearch) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );

RxBinding

RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );

RxBinding

RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );

RxBinding

▪ compile ‘com.jakewharton.rxbinding:rxbinding:0.4.0'

▪ compile ‘com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0'

▪ compile ‘com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.4.0'

▪ compile ‘com.jakewharton.rxbinding:rxbinding-design:0.4.0'

▪ compile ‘com.jakewharton.rxbinding:rxbinding-recyclerview-v7:0.4.0'

▪ compile 'com.jakewharton.rxbinding:rxbinding-leanback-v17:0.4.0'

Retrofit

Retrofit

▪ sync or async API

@GET("group/{id}/users")Call<List<User>> groupList(@Path("id") int groupId);

Retrofit

▪ reactive API

@GET("group/{id}/users")Observable<List<User>> groupList(@Path("id") int groupId);

Retrofit

– onNext() with Response, then onComplete()

– onError() in case of error

@GET("/data")Observable<Response> getData( @Body DataRequest dataRequest);

Retrofit

RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );

SQLBrite

THE END

top related