Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Practical RxJava for Android

1,128 views

Published on

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.

Published in: Technology
  • Be the first to comment

Practical RxJava for Android

  1. 1. Practical RxJava for Android Tomáš Kypta @TomasKypta
  2. 2. What’s RxJava?
  3. 3. What’s RxJava? • composable data flow • push concept • combination of • observer pattern • iterator pattern • functional programming
  4. 4. RxJava…not sure if…
  5. 5. Typical App Event Source Views Network DB … Listener Listener Listener Listener logic logiclogiclogic State
  6. 6. Reactive App Transformation Event Source Observable Observable Observable Observable … ObserverObserver Views Network DB
  7. 7. RxJava data flow Observable .from(new String[]{"Hello", "Droidcon!"}) creation
  8. 8. 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
  9. 9. 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
  10. 10. 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
  11. 11. Java 8 & Android • use Retrolambda • or jack compiler • no support for annotation processors • ButterKnife • Dagger • AutoValue
  12. 12. 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
  13. 13. 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));
  14. 14. Key parts • Observable • Observer or Subscriber • onNext(T) • onCompleted() • onError(Throwable) • Subject
  15. 15. 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
  16. 16. Marble diagrams • RxMarbles
  17. 17. Subscription
  18. 18. Subscription Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() );
  19. 19. Subscription Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() ); s.unsubscribe();
  20. 20. Subscription Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() ); s.unsubscribe(); Timber.i("unsubscribed: " + s.isUnsubscribed());
  21. 21. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription();
  22. 22. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription);
  23. 23. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription); compositeSubscription.unsubscribe();
  24. 24. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription); compositeSubscription.unsubscribe(); compositeSubscription.clear();
  25. 25. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription); compositeSubscription.unsubscribe(); compositeSubscription.clear();
  26. 26. Bridging non-Rx APIs
  27. 27. Bridging non-Rx APIs • create() private Object getData() {...} public Observable<Object> getObservable() { return Observable.create(subscriber -> { subscriber.onNext(getData()); subscriber.onCompleted(); }); }
  28. 28. Bridging non-Rx APIs • just() private Object getData() {...} public Observable<Object> getObservable() { return Observable.just(getData()); } !
  29. 29. just() & defer() • defer() private Object getData() {...} public Observable<Object> getObservable() { return Observable.defer( () -> Observable.just(getData()) ); }
  30. 30. Subject
  31. 31. Subject • Observable & Observer • bridge between non-Rx API • stateful • terminal state • don’t pass data after onComplete() or onError()
  32. 32. Subject • use carefully • cannot be reused • always create a new instance when subscribing to an Observable
  33. 33. Subject Subject subject = … subject.subscribe(subscriber); subject.onNext(A); subject.onNext(B);
  34. 34. RxRelay
  35. 35. RxRelay • Relay = Subject - onComplete() - onError() • Relay = Observable & Action1 • call() instead of onNext()
  36. 36. Subject vs. RxRelay • Subject • stateful • Relay • stateless
  37. 37. RxRelay Relay relay = … relay.subscribe(observer); relay.call(A); relay.call(B);
  38. 38. Threading
  39. 39. Threading • Parts of a data flow can run on different threads! • in RxJava defined by Schedulers
  40. 40. Schedulers • computation() • io() • newThread() • from(Executor)
  41. 41. Android Schedulers • mainThread() • from(Looper)
  42. 42. Threading • operators have default Schedulers • check Javadoc
  43. 43. Threading • just() • current thread • delay(long, TimeUnit) • computation() thread
  44. 44. Threading • subscribeOn(Scheduler) • subscribes to Observable on the Scheduler • observeOn(Scheduler) • Observable emits on the Scheduler
  45. 45. subscribeOn() • multiple calls useless • only the first call works! • for all operators
  46. 46. observeOn() • can be called multiple times
  47. 47. Async composition
  48. 48. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken))
  49. 49. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser())
  50. 50. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser()) .flatMap(user -> serviceEndpoint.getUserContact(user.getId()))
  51. 51. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser()) .flatMap(user -> serviceEndpoint.getUserContact(user.getId()))
  52. 52. Async composition
  53. 53. Android & RxJava
  54. 54. Android Lifecycle • few complications • continuing subscription during configuration change • memory leaks
  55. 55. Android Lifecycle • continuing subscription during configuration change • replay() • don’t use cache()
  56. 56. Android Lifecycle • memory leaks • bind to Activity/Fragment lifecycle • use RxLifecycle
  57. 57. RxLifecycle
  58. 58. RxLifecycle • auto unsubscribe based on Activity/Fragment lifecycle myObservable .compose( RxLifecycle.bindUntilEvent( lifecycleObservable, ActivityEvent.DESTROY ) ) .subscribe(…); myObservable .compose(RxLifecycle.bindActivity(lifecycleObs)) .subscribe(…);
  59. 59. RxLifecycle • How to obtain ActivityEvent or FragmentEvent? A. rxlifecycle-components + subclass RxActivity, RxFragment B. Navi + rxlifecycle-navi C. Write it yourself
  60. 60. RxLifecycle public class MyActivity extends RxActivity { @Override public void onResume() { super.onResume(); myObservable .compose(bindToLifecycle()) .subscribe(); } }
  61. 61. RxLifecycle & Navi public class MyActivity extends NaviActivity { private final ActivityLifecycleProvider provider = NaviLifecycle.createActivityLifecycleProvider(this); }
  62. 62. 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(…); } }
  63. 63. Navi naviComponent.addListener(Event.CREATE, new Listener<Bundle>() { @Override public void call(Bundle bundle) { setContentView(R.layout.main); } } );
  64. 64. RxNavi RxNavi .observe(naviComponent, Event.CREATE) .subscribe(bundle -> setContentView(R.layout.main)); • converting lifecycle callbacks into Observables!
  65. 65. RxLifecycle public class MainActivityFragment extends Fragment { BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create(); }
  66. 66. RxLifecycle public class MainActivityFragment extends Fragment { BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create(); @Override public void onPause() { super.onPause(); Timber.i("onPause"); mLifecycleSubject.onNext(FragmentEvent.PAUSE); } }
  67. 67. 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(…); } }
  68. 68. RxBinding
  69. 69. RxBinding RxView.clicks(vBtnSearch) .subscribe( v -> { Intent intent = new Intent(getActivity(), SearchActivity.class); startActivity(intent); } );
  70. 70. RxBinding RxTextView.textChanges(vEtSearch) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );
  71. 71. RxBinding RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );
  72. 72. 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) );
  73. 73. Retrofit
  74. 74. 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
  75. 75. Retrofit • onNext() with Response, then onComplete() • onError() in case of error @GET("/data") Observable<Response> getData( @Body DataRequest dataRequest);
  76. 76. 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) );
  77. 77. 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) );
  78. 78. SQLBrite
  79. 79. SQLBrite • by Square • wrapper around SQLiteOpenHelper and ContentResolver • not an ORM • not type-safe query mechanism
  80. 80. SQLBrite SqlBrite sqlBrite = SqlBrite.create();
  81. 81. SQLBrite SqlBrite sqlBrite = SqlBrite.create(); BriteDatabase db = sqlBrite .wrapDatabaseHelper(openHelper, Schedulers.io());
  82. 82. SQLBrite SqlBrite sqlBrite = SqlBrite.create(); BriteDatabase db = sqlBrite .wrapDatabaseHelper(openHelper, Schedulers.io()); Observable<Query> users = db .createQuery("users", "SELECT * FROM users");
  83. 83. 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()
  84. 84. Antipatterns & Common Mistakes
  85. 85. Antipatterns • subscribing to an Observable inside an action or function
  86. 86. Antipatterns Subscription buttonSubscription = RxView.clicks(vButton) .subscribe(v -> { Timber.i("button click"); }); mCompositeSubscription.add(buttonSubscription);
  87. 87. 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);
  88. 88. 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);
  89. 89. 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);
  90. 90. 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));
  91. 91. Rules of Thumb • don’t write your own operators if possible • prefers stateless constructs over stateful variants
  92. 92. Exception Handling
  93. 93. onError() • use for unexpected exceptions • unrecoverable stream • because it’s terminal event • useless stack traces when missing
  94. 94. Expected Exceptions • make an Observable of a response wrapper
  95. 95. Exception Handling • recovering from bad usage of onError() • onErrorReturn() • convert to onNext() • onErrorResumeNext() • convert to Observable
  96. 96. Unit Testing with RxJava
  97. 97. Unit Testing with RxJava • piece of cake • compared to unit testing AsyncTasks • turn methods synchronous with toBlocking() • BlockingObservable
  98. 98. Q&A
  99. 99. 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

×