reactive programming on android
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