Practical RxJava for Android Tomáš Kypta @TomasKypta
Practical RxJava for AndroidTomáš Kypta @TomasKypta
What’s RxJava?
What’s RxJava?• composable data flow
• push concept
• combination of
• observer pattern
• iterator pattern
• functional programming
RxJava…not sure how Android apps…
Typical non-reactive appEvent Source
Views Network DB …
Listener Listener Listener Listener
logic logiclogiclogic
State
Reactive app
Transformation
Event Source
Observable Observable Observable Observable
…
ObserverObserver
Views Network DB
RxJava data flowObservable .from(new String[]{"Hello", "Droidcon!"})
creation
RxJava data flowObservable .from(new String[]{"Hello", "Droidcon!"}) .map(new Func1<String, String>() { @Override public String call(String s) { return s.toUpperCase(Locale.getDefault()); } })
creation
RxJava data flowObservable .from(new String[]{"Hello", "Droidcon!"}) .map(new Func1<String, String>() { @Override public String call(String s) { return s.toUpperCase(Locale.getDefault()); } }) .reduce(new Func2<String, String, String>() { @Override public String call(String s, String s2) { return s + ' ' + s2; } })
creation
transformation
RxJava data flowObservable .from(new String[]{"Hello", "Droidcon!"}) .map(new Func1<String, String>() { @Override public String call(String s) { return s.toUpperCase(Locale.getDefault()); } }) .reduce(new Func2<String, String, String>() { @Override public String call(String s, String s2) { return s + ' ' + s2; } }) .subscribe(new Action1<String>() { @Override public void call(String s) { Timber.i(s); } });
creation
transformation
subscription
Java 8 & Android
• use Retrolambda
• or jack compiler
• usable since build plugin 2.2.0
RxJava data flowObservable .from(new String[]{"Hello", "Droidcon!"}) .map(new Func1<String, String>() { @Override public String call(String s) { return s.toUpperCase(Locale.getDefault()); } }) .reduce(new Func2<String, String, String>() { @Override public String call(String s, String s2) { return s + ' ' + s2; } }) .subscribe(new Action1<String>() { @Override public void call(String s) { Timber.i(s); } });
creation
transformation
subscription
RxJava data flow with Java 8
creationtransformationsubscription
Observable .from(new String[]{"Hello", "Droidcon!"}) .map(s -> s.toUpperCase(Locale.getDefault())) .reduce((s,s2) -> s + ' ' + s2) .subscribe(s -> Timber.i(s));
Key parts• Observable
• Observer or Subscriber
• onNext(T)
• onCompleted()
• onError(Throwable)
• Subject
What is RxJava good for?
• making code simple and readable
• …with Java 8
• Async processing
• no AsyncTask, AsyncTaskLoader, …
Why…??
Async composition
• Async composition
• RxJava offers simple chaining of async operations
• eliminates callback hell
RxJava 1 vs. RxJava 2
RxJava 1 vs. RxJava 2• RxJava 2 in RC now
• limited support in libraries
• they will coexist for some time
• different group ids
• io.reactivex vs. io.reactivex.rxjava2
• different package names
• rx vs. io.reactivex
• https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0
Operators
Operators
• work on Observable
• return an Observable
• can be chained
Common mistakes• assuming mutability
• operators return new Observable
Observable<String> observable = Observable.from(new String[]{"Hello", "Droidcon!"});obserable.map(s -> s.toUpperCase(Locale.getDefault()));obserable.reduce((s,s2) -> s + ' ' + s2);
Common Mistakes•assuming mutability • operators return new Observable
Observable<String> observable = Observable.from(new String[]{"Hello", "Droidcon!"}) .map(s -> s.toUpperCase(Locale.getDefault())) .reduce((s,s2) -> s + ' ' + s2);
Marble diagrams• RxMarbles
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());
Subscription
Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() );
s.unsubscribe();Timber.i("unsubscribed: " + s.isUnsubscribed());
CompositeSubscription
CompositeSubscription compositeSubscription = new CompositeSubscription();
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();
CompositeSubscription
CompositeSubscription compositeSubscription = new CompositeSubscription();
compositeSubscription.add(subscription);
compositeSubscription.unsubscribe();
compositeSubscription.clear();
Bridging non-Rx APIs
Observable creation• create()
• better to avoid
private Object getData() {...}
public Observable<Object> getObservable() { return Observable.create(subscriber -> { subscriber.onNext(getData()); subscriber.onCompleted(); });}
Observable creation• just()
private Object getData() {...}
public Observable<Object> getObservable() { return Observable.just(getData());} !
just() & defer()• defer()
private Object getData() {...}
public Observable<Object> getObservable() { return Observable.defer( () -> Observable.just(getData()) );}
Observable creation• fromCallable()
• callable invoked when an observer subscribes
private Object getData() {…}
public Observable<Object> getObservable() { return Observable.fromCallable(new Callable<Object>() { @Override public String call() throws Exception { return getData(); } });}
Observable creation from async APIs• fromEmitter()Observable.<Event>fromEmitter(emitter -> { Callback listener = new Callback() { @Override public void onSuccess(Event e) { emitter.onNext(e); if (e.isLast()) emitter.onCompleted(); } @Override public void onFailure(Exception e) { emitter.onError(e); } }; AutoCloseable c = api.someAsyncMethod(listener); emitter.setCancellation(c::close);}, BackpressureMode.BUFFER);
Subject
Subject
• Observable & Observer
• bridge between non-Rx API
Subject
// consumeanObservable.subscribe(aSubject);
// produceaSubject.subscribe(aSubscriber);
Subject
// bridging & multicastingaSubject.subscribe(subscriber1);aSubject.subscribe(subscriber2);
aSubject.onNext(item1);aSubject.onNext(item2);aSubject.onNext(item2);
aSubject.onCompleted()
Subject
• stateful
• terminal state
• don’t pass data after onComplete() or onError()
Subject
• AsyncSubject
• BehaviorSubject
• ReplaySubject
• PublishSubject
• SerializedSubject
RxRelay
RxRelay
• safer cousin of Subject
• https://github.com/JakeWharton/RxRelay
RxRelay
• Relay = Subject - onComplete() - onError()
• Relay = Observable & Action1
• call() instead of onNext()
Subject vs. RxRelay
• Subject
• stateful
• Relay
• stateless
RxRelay
Relay relay = …relay.subscribe(observer);relay.call(A);relay.call(B);
Threading
Threading
• Parts of a data flow can run on different threads!
• Threading in RxJava defined by Schedulers
Schedulers
• computation()
• io()
• newThread()
• from(Executor)
Android Schedulers
• mainThread()
• from(Looper)
Threading
• operators have their default Schedulers
• check Javadoc!
Threading
• just()
• current thread
• delay(long, TimeUnit)
• computation() thread
Threading
• subscribeOn(Scheduler)
• subscribes to Observable on the Scheduler
• observeOn(Scheduler)
• Observable emits on the Scheduler
subscribeOn()
• multiple calls useless
• only the first call works!
• for all operators
observeOn()
• can be called multiple times
• changes Scheduler downstream
Side effect methods
Side effect methods• doOnNext()
• doOnError()
• doOnCompleted()
• doOnSubscribe()
• doOnUnsubscribe()
• …
Async composition
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()))
Async composition
apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser()) .flatMap(user -> serviceEndpoint.getUserContact(user.getId()))
Async composition
Android & RxJava
Android Lifecycle
• few complications
• continuing subscription during configuration change
• memory leaks
Android Lifecycle
• continuing subscription during configuration change
• replay()
• don’t use cache()
Android Lifecycle
• memory leaks
• bind to Activity/Fragment lifecycle
• use RxLifecycle
RxLifecycle
RxLifecycle• auto unsubscribe based on Activity/Fragment
lifecyclemyObservable .compose( RxLifecycle.bindUntilEvent( lifecycleObservable, ActivityEvent.DESTROY ) ) .subscribe(…);
myObservable .compose(RxLifecycle.bindActivity(lifecycleObs)) .subscribe(…);
RxLifecycle• How to obtain ActivityEvent or FragmentEvent?
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 & Navi
Navi• https://github.com/trello/navi
• better listeners for Activity/Fragment events
• decoupling code from Activity/Fragment
naviComponent.addListener(Event.CREATE, new Listener<Bundle>() { @Override public void call(Bundle bundle) { setContentView(R.layout.main); } });
• converting lifecycle callbacks into Observables!
RxNavi
RxNavi .observe(naviComponent, Event.CREATE) .subscribe(bundle -> setContentView(R.layout.main));
RxLifecycle & Navi
public class MyActivity extends NaviActivity { private final ActivityLifecycleProvider provider = NaviLifecycle.createActivityLifecycleProvider(this);}
RxLifecycle & Navi
public class MyActivity extends NaviActivity { private final ActivityLifecycleProvider provider = NaviLifecycle.createActivityLifecycleProvider(this);
@Override public void onResume() { super.onResume(); myObservable .compose(provider.bindToLifecycle()) .subscribe(…); }}
RxLifecycle & custom solutionpublic class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();
}
RxLifecycle & custom solutionpublic class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();
@Override public void onPause() { super.onPause(); Timber.i("onPause"); mLifecycleSubject.onNext(FragmentEvent.PAUSE); }}
public class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();
@Override public void onPause() { super.onPause(); Timber.i("onPause"); mLifecycleSubject.onNext(FragmentEvent.PAUSE); }
@Override public void onResume() { super.onResume(); myObservable // e.g. UI events Observable .compose( RxLifecycle .bindUntilEvent(mLifecycleSubject, FragmentEvent.PAUSE)) .doOnUnsubscribe(() -> Timber.i("onUnsubscribe")) .subscribe(…); }}
RxLifecycle & custom solution
RxBinding
RxBinding
RxView.clicks(vBtnSearch) .subscribe( v -> { Intent intent = new Intent(getActivity(), SearchActivity.class); startActivity(intent); } );
RxBinding
RxTextView.textChanges(vEtSearch) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .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
RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .flatMap(response -> Observable.from(response.getItems()) .subscribe( s -> Timber.i("item: " + s) );
Retrofit
Retrofit• sync or async API (Retrofit 2)
@GET("group/{id}/users")Call<List<User>> groupList(@Path("id") int groupId);
@GET("group/{id}/users")Observable<List<User>> groupList(@Path("id") int groupId);
• reactive API
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()) ) .flatMap(list -> Observable.from(list)) .subscribe( s -> Timber.i("item: " + s) );
Retrofit
RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString()) ) .flatMap(list -> Observable.from(list)) .subscribe( s -> Timber.i("item: " + s) );
RxJava & SQLite• use SQLBrite
• wrapper around SQLiteOpenHelper and ContentResolver
SqlBrite sqlBrite = SqlBrite.create();
BriteDatabase db = sqlBrite .wrapDatabaseHelper(openHelper, Schedulers.io());
Observable<Query> users = db .createQuery("users", "SELECT * FROM users");
References• https://github.com/ReactiveX/RxJava
• https://github.com/ReactiveX/RxAndroid
• https://github.com/JakeWharton/RxRelay
• https://github.com/trello/RxLifecycle
• https://github.com/trello/navi
• https://github.com/JakeWharton/RxBinding
• https://github.com/square/retrofit
• https://github.com/square/sqlbrite
• advanced RxJava blog https://akarnokd.blogspot.com
• http://slides.com/yaroslavheriatovych/frponandroid
Q&A