Practical RxJava for Android

97
Practical RxJava for Android Tomáš Kypta @TomasKypta

Transcript of Practical RxJava for Android

Page 1: Practical RxJava for Android

Practical RxJava for AndroidTomáš Kypta @TomasKypta

Page 2: Practical RxJava for Android

What’s RxJava?

Page 3: Practical RxJava for Android

What’s RxJava?• composable data flow

• push concept

• combination of

• observer pattern

• iterator pattern

• functional programming

Page 4: Practical RxJava for Android

RxJava…not sure how Android apps…

Page 5: Practical RxJava for Android

Typical non-reactive appEvent Source

Views Network DB …

Listener Listener Listener Listener

logic logiclogiclogic

State

Page 6: Practical RxJava for Android

Reactive app

Transformation

Event Source

Observable Observable Observable Observable

ObserverObserver

Views Network DB

Page 7: Practical RxJava for Android

RxJava data flowObservable .from(new String[]{"Hello", "Droidcon!"})

creation

Page 8: Practical RxJava for Android

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

Page 9: Practical RxJava for Android

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

Page 10: Practical RxJava for Android

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

Page 11: Practical RxJava for Android

Java 8 & Android

• use Retrolambda

• or jack compiler

• usable since build plugin 2.2.0

Page 12: Practical RxJava for Android

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

Page 13: Practical RxJava for Android

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));

Page 14: Practical RxJava for Android

Key parts• Observable

• Observer or Subscriber

• onNext(T)

• onCompleted()

• onError(Throwable)

• Subject

Page 15: Practical RxJava for Android

What is RxJava good for?

• making code simple and readable

• …with Java 8

• Async processing

• no AsyncTask, AsyncTaskLoader, …

Page 16: Practical RxJava for Android

Why…??

Page 17: Practical RxJava for Android

Async composition

• Async composition

• RxJava offers simple chaining of async operations

• eliminates callback hell

Page 18: Practical RxJava for Android

RxJava 1 vs. RxJava 2

Page 19: Practical RxJava for Android

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

Page 20: Practical RxJava for Android

Operators

Page 21: Practical RxJava for Android

Operators

• work on Observable

• return an Observable

• can be chained

Page 22: Practical RxJava for Android

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);

Page 23: Practical RxJava for Android

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);

Page 24: Practical RxJava for Android

Marble diagrams• RxMarbles

Page 25: Practical RxJava for Android

Subscription

Page 26: Practical RxJava for Android

Subscription

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

Page 27: Practical RxJava for Android

Subscription

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

s.unsubscribe();

Page 28: Practical RxJava for Android

Subscription

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

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

Page 29: Practical RxJava for Android

Subscription

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

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

Page 30: Practical RxJava for Android

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

Page 31: Practical RxJava for Android

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

Page 32: Practical RxJava for Android

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

compositeSubscription.unsubscribe();

Page 33: Practical RxJava for Android

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

compositeSubscription.unsubscribe();

compositeSubscription.clear();

Page 34: Practical RxJava for Android

CompositeSubscription

CompositeSubscription compositeSubscription = new CompositeSubscription();

compositeSubscription.add(subscription);

compositeSubscription.unsubscribe();

compositeSubscription.clear();

Page 35: Practical RxJava for Android

Bridging non-Rx APIs

Page 36: Practical RxJava for Android

Observable creation• create()

• better to avoid

private Object getData() {...}

public Observable<Object> getObservable() { return Observable.create(subscriber -> { subscriber.onNext(getData()); subscriber.onCompleted(); });}

Page 37: Practical RxJava for Android

Observable creation• just()

private Object getData() {...}

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

Page 38: Practical RxJava for Android

just() & defer()• defer()

private Object getData() {...}

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

Page 39: Practical RxJava for Android

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(); } });}

Page 40: Practical RxJava for Android

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);

Page 41: Practical RxJava for Android

Subject

Page 42: Practical RxJava for Android

Subject

• Observable & Observer

• bridge between non-Rx API

Page 43: Practical RxJava for Android

Subject

// consumeanObservable.subscribe(aSubject);

// produceaSubject.subscribe(aSubscriber);

Page 44: Practical RxJava for Android

Subject

// bridging & multicastingaSubject.subscribe(subscriber1);aSubject.subscribe(subscriber2);

aSubject.onNext(item1);aSubject.onNext(item2);aSubject.onNext(item2);

aSubject.onCompleted()

Page 45: Practical RxJava for Android

Subject

• stateful

• terminal state

• don’t pass data after onComplete() or onError()

Page 46: Practical RxJava for Android

Subject

• AsyncSubject

• BehaviorSubject

• ReplaySubject

• PublishSubject

• SerializedSubject

Page 47: Practical RxJava for Android

RxRelay

Page 48: Practical RxJava for Android

RxRelay

• safer cousin of Subject

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

Page 49: Practical RxJava for Android

RxRelay

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

• Relay = Observable & Action1

• call() instead of onNext()

Page 50: Practical RxJava for Android

Subject vs. RxRelay

• Subject

• stateful

• Relay

• stateless

Page 51: Practical RxJava for Android

RxRelay

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

Page 52: Practical RxJava for Android

Threading

Page 53: Practical RxJava for Android

Threading

• Parts of a data flow can run on different threads!

• Threading in RxJava defined by Schedulers

Page 54: Practical RxJava for Android

Schedulers

• computation()

• io()

• newThread()

• from(Executor)

Page 55: Practical RxJava for Android

Android Schedulers

• mainThread()

• from(Looper)

Page 56: Practical RxJava for Android

Threading

• operators have their default Schedulers

• check Javadoc!

Page 57: Practical RxJava for Android

Threading

• just()

• current thread

• delay(long, TimeUnit)

• computation() thread

Page 58: Practical RxJava for Android

Threading

• subscribeOn(Scheduler)

• subscribes to Observable on the Scheduler

• observeOn(Scheduler)

• Observable emits on the Scheduler

Page 59: Practical RxJava for Android

subscribeOn()

• multiple calls useless

• only the first call works!

• for all operators

Page 60: Practical RxJava for Android

observeOn()

• can be called multiple times

• changes Scheduler downstream

Page 61: Practical RxJava for Android

Side effect methods

Page 62: Practical RxJava for Android

Side effect methods• doOnNext()

• doOnError()

• doOnCompleted()

• doOnSubscribe()

• doOnUnsubscribe()

• …

Page 63: Practical RxJava for Android

Async composition

Page 64: Practical RxJava for Android

Async composition

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

Page 65: Practical RxJava for Android

Async composition

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

Page 66: Practical RxJava for Android

Async composition

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

Page 67: Practical RxJava for Android

Async composition

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

Page 68: Practical RxJava for Android

Async composition

Page 69: Practical RxJava for Android

Android & RxJava

Page 70: Practical RxJava for Android

Android Lifecycle

• few complications

• continuing subscription during configuration change

• memory leaks

Page 71: Practical RxJava for Android

Android Lifecycle

• continuing subscription during configuration change

• replay()

• don’t use cache()

Page 72: Practical RxJava for Android

Android Lifecycle

• memory leaks

• bind to Activity/Fragment lifecycle

• use RxLifecycle

Page 73: Practical RxJava for Android

RxLifecycle

Page 74: Practical RxJava for Android

RxLifecycle• auto unsubscribe based on Activity/Fragment

lifecyclemyObservable .compose( RxLifecycle.bindUntilEvent( lifecycleObservable, ActivityEvent.DESTROY ) ) .subscribe(…);

myObservable .compose(RxLifecycle.bindActivity(lifecycleObs)) .subscribe(…);

Page 75: Practical RxJava for Android

RxLifecycle• How to obtain ActivityEvent or FragmentEvent?

A. rxlifecycle-components + subclass RxActivity, RxFragment

B. Navi + rxlifecycle-navi

C. Write it yourself

Page 76: Practical RxJava for Android

RxLifecycle

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

Page 77: Practical RxJava for Android

RxLifecycle & Navi

Page 78: Practical RxJava for Android

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); } });

Page 79: Practical RxJava for Android

• converting lifecycle callbacks into Observables!

RxNavi

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

Page 80: Practical RxJava for Android

RxLifecycle & Navi

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

Page 81: Practical RxJava for Android

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(…); }}

Page 82: Practical RxJava for Android

RxLifecycle & custom solutionpublic class MainActivityFragment extends Fragment {

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

}

Page 83: Practical RxJava for Android

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); }}

Page 84: Practical RxJava for Android

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

Page 85: Practical RxJava for Android

RxBinding

Page 86: Practical RxJava for Android

RxBinding

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

Page 87: Practical RxJava for Android

RxBinding

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

Page 88: Practical RxJava for Android

RxBinding

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

Page 89: Practical RxJava for Android

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) );

Page 90: Practical RxJava for Android

Retrofit

Page 91: Practical RxJava for Android

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

Page 92: Practical RxJava for Android

Retrofit• onNext() with Response, then onComplete()

• onError() in case of error

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

Page 93: Practical RxJava for Android

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) );

Page 94: Practical RxJava for Android

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) );

Page 95: Practical RxJava for Android

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");

Page 96: Practical RxJava for Android

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

Page 97: Practical RxJava for Android

Q&A