Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Practical RxJava for Android

Practical RxJava for Android

A practical guide to using RxJava on Android. Tips for improving your app architecture with reactive programming. What are the advantages and disadvantages of using RxJava over standard architecture? And how to connect with other popular Android libraries?
Presented at Droidcon Greece 2016.

Tomáš Kypta

July 09, 2016
Tweet

More Decks by Tomáš Kypta

Other Decks in Programming

Transcript

  1. What’s RxJava? • composable data flow • push concept •

    combination of • observer pattern • iterator pattern • functional programming
  2. Typical App Event Source Views Network DB … Listener Listener

    Listener Listener logic logic logic logic State
  3. RxJava data flow Observable .from(new String[]{"Hello", "Droidcon!"}) .map(new Func1<String, String>()

    { @Override public String call(String s) { return s.toUpperCase(Locale.getDefault()); } }) creation
  4. RxJava data flow Observable .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
  5. RxJava data flow Observable .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
  6. Java 8 & Android • use Retrolambda • or jack

    compiler • no support for annotation processors • ButterKnife • Dagger • AutoValue
  7. RxJava data flow Observable .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
  8. RxJava flow with Java8 creation transformation subscription Observable .from(new String[]{"Hello",

    "Droidcon!"}) .map(s -> s.toUpperCase(Locale.getDefault())) .reduce((s,s2) -> s + ' ' + s2) .subscribe(s -> Timber.i(s));
  9. Key parts • Observable • Observer or Subscriber • onNext(T)

    • onCompleted() • onError(Throwable) • Subject
  10. What is RxJava good for? • making code simple and

    readable • Async processing • no AsyncTask, AsyncTaskLoader, … • Async composition • RxJava offers simple chaining of async operations • eliminates callback hell
  11. Bridging non-Rx APIs • create() private Object getData() {...} public

    Observable<Object> getObservable() { return Observable.create(subscriber -> { subscriber.onNext(getData()); subscriber.onCompleted(); }); }
  12. Bridging non-Rx APIs • just() private Object getData() {...} public

    Observable<Object> getObservable() { return Observable.just(getData()); } !
  13. just() & defer() • defer() private Object getData() {...} public

    Observable<Object> getObservable() { return Observable.defer( () -> Observable.just(getData()) ); }
  14. Subject • Observable & Observer • bridge between non-Rx API

    • stateful • terminal state • don’t pass data after onComplete() or onError()
  15. Subject • use carefully • cannot be reused • always

    create a new instance when subscribing to an Observable
  16. RxRelay • Relay = Subject - onComplete() - onError() •

    Relay = Observable & Action1 • call() instead of onNext()
  17. Threading • Parts of a data flow can run on

    different threads! • in RxJava defined by Schedulers
  18. Threading • subscribeOn(Scheduler) • subscribes to Observable on the Scheduler

    • observeOn(Scheduler) • Observable emits on the Scheduler
  19. RxLifecycle • auto unsubscribe based on Activity/Fragment lifecycle myObservable .compose(

    RxLifecycle.bindUntilEvent( lifecycleObservable, ActivityEvent.DESTROY ) ) .subscribe(…); myObservable .compose(RxLifecycle.bindActivity(lifecycleObs)) .subscribe(…);
  20. RxLifecycle • How to obtain ActivityEvent or FragmentEvent? A. rxlifecycle-components

    + subclass RxActivity, RxFragment B. Navi + rxlifecycle-navi C. Write it yourself
  21. RxLifecycle public class MyActivity extends RxActivity { @Override public void

    onResume() { super.onResume(); myObservable .compose(bindToLifecycle()) .subscribe(); } }
  22. RxLifecycle & Navi public class MyActivity extends NaviActivity { private

    final ActivityLifecycleProvider provider = NaviLifecycle.createActivityLifecycleProvider(this); }
  23. 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(…); } }
  24. RxLifecycle public class MainActivityFragment extends Fragment { BehaviorSubject<FragmentEvent> mLifecycleSubject =

    BehaviorSubject.create(); @Override public void onPause() { super.onPause(); Timber.i("onPause"); mLifecycleSubject.onNext(FragmentEvent.PAUSE); } }
  25. RxLifecycle 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(…); } }
  26. RxBinding RxView.clicks(vBtnSearch) .subscribe( v -> { Intent intent = new

    Intent(getActivity(), SearchActivity.class); startActivity(intent); } );
  27. 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
  28. Retrofit • onNext() with Response, then onComplete() • onError() in

    case of error @GET("/data") Observable<Response> getData( @Body DataRequest dataRequest);
  29. SQLBrite SqlBrite sqlBrite = SqlBrite.create(); BriteDatabase db = sqlBrite .wrapDatabaseHelper(openHelper,

    Schedulers.io()); Observable<Query> users = db .createQuery("users", "SELECT * FROM users");
  30. SQLBrite • subscribed queries updated when insert/update/delete occurs • necessary

    to run it through the same BriteDatabase object • can cause backpressure • fix by using transactions = one update • or use debounce()
  31. Antipatterns Subscription buttonSubscription = RxView.clicks(vButton) .subscribe(v -> { Timber.i("button click");

    Subscription s = mAppInfoProvider.getAppInfoObservable() .subscribe( appInfo -> Timber.i(appInfo.packageName()), e -> Timber.e(e.getMessage()) ); // and when do we unsubscribe s? }); mCompositeSubscription.add(buttonSubscription);
  32. Antipatterns • correct solution Subscription buttonSubscription = RxView.clicks(vButton) .flatMap(v ->

    mAppInfoProvider.getAppInfoObservable()) .subscribe( appInfo -> Timber.i(appInfo.packageName()), e -> Timber.e(e.getMessage()) ); mCompositeSubscription.add(buttonSubscription);
  33. Antipatterns • correct solution Subscription buttonSubscription = RxView.clicks(vButton) .flatMap(v ->

    mAppInfoProvider.getAppInfoObservable()) .subscribe( appInfo -> Timber.i(appInfo.packageName()), e -> Timber.e(e.getMessage()) ); mCompositeSubscription.add(buttonSubscription);
  34. 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); obserable.subscribe(s -> Timber.i(s));
  35. Rules of Thumb • don’t write your own operators if

    possible • prefers stateless constructs over stateful variants
  36. onError() • use for unexpected exceptions • unrecoverable stream •

    because it’s terminal event • useless stack traces when missing
  37. Exception Handling • recovering from bad usage of onError() •

    onErrorReturn() • convert to onNext() • onErrorResumeNext() • convert to Observable
  38. Unit Testing with RxJava • piece of cake • compared

    to unit testing AsyncTasks • turn methods synchronous with toBlocking() • BlockingObservable
  39. Q&A

  40. 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 • http://slides.com/yaroslavheriatovych/frponandroid