KEMBAR78
Reactive programming on Android | PDF
Reactive programming on Android
Tomáš 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);
}
});
Example
Observable.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 lifecycle
myObservable
.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();
}
}
RxLifecycle
public 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));
RxLifecycle
public class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();
RxLifecycle
public 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(…);
}
}
RxLifecycle
public 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

Reactive programming on Android