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.

Reactive programming on Android

479 views

Published on

Lecture on Reactive programming on Android, mDevCamp 2016.
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?

Published in: Technology
  • Be the first to comment

Reactive programming on Android

  1. 1. Reactive programming on Android Tomáš Kypta
  2. 2. What’s RxJava
  3. 3. What’s RxJava ▪ combination of – observer pattern – iterator pattern – functional programming ▪ composable data flow ▪ push concept
  4. 4. Key Parts ▪ Observable ▪ Observer, Subscriber ▪ Subject
  5. 5. Key Methods ▪ onNext(T) ▪ onCompleted() ▪ onError(Throwable)
  6. 6. Key Methods ▪ subscribers can be replaced with Action parameters someObservable.subscribe( onNextAction, onErrorAction, onCompleteAction );
  7. 7. Key Methods ▪ we often ignore onError() and onComplete() ▪ risk of crashes when error occurs someObservable.subscribe( onNextAction );
  8. 8. What is RxJava good for? ▪ Async processing – AsyncTask – AsyncTaskLoader – …
  9. 9. What is RxJava good for? ▪ Async composition – nested API calls – AsyncTask callback hell – There’s no way to do it right!
  10. 10. What is RxJava good for? ▪ Testability
  11. 11. Pros & Cons ▪ powerful tool ▪ conceptually difficult – there are some tricky parts
  12. 12. RxJava & app architecture ▪ suitable both for MVP and MVVM
  13. 13. Example Observable.just("Hello, world!") .subscribe(new Action1<String>() { @Override public void call(String s) { Timber.d(s); } });
  14. 14. 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); } });
  15. 15. Retrolambda ▪ Enables Java 8 lambdas on Android
  16. 16. 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); } });
  17. 17. 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) {} });
  18. 18. 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) {} });
  19. 19. Operators
  20. 20. Operators ▪ creation ▪ filtering ▪ combining ▪ transforming ▪ …
  21. 21. Understanding operators ▪ marble diagrams ▪ RxMarbles – rxmarbles.com – Android app
  22. 22. Observable Creation ▪ create() ▪ from() ▪ just()
  23. 23. Observable Creation ▪ corner cases ▪ empty() ▪ never() ▪ error()
  24. 24. Filtering ▪ filter() ▪ takeLast() ▪ take()
  25. 25. Filtering ▪ first(), take(int) ▪ skip() ▪ distinct() ▪ …
  26. 26. Combining ▪ merge() ▪ zip()
  27. 27. Transforming ▪ map() ▪ flatMap() ▪ concatMap()
  28. 28. map vs. flatMap vs. concatMap ▪ map – A -> B – one in, one out ▪ flatMap – A -> Observable<B> – map + flatten ▪ concatMap – ordered flatMap
  29. 29. Aggregate ▪ concat() ▪ reduce
  30. 30. Operators ▪ and there’s much more …
  31. 31. Example Observable.from(myUrls)
  32. 32. Example Observable.from(myUrls) .filter(url -> url.endsWith(".cz"))
  33. 33. Example Observable.from(myUrls) .filter(url -> url.endsWith(".cz")) .flatMap(url -> Observable.just(getHttpStatus(url)))
  34. 34. Example Observable.from(myUrls) .filter(url -> url.endsWith(".cz")) .flatMap(url -> Observable.just(getHttpStatus(url))) .take(5)
  35. 35. Example Observable.from(myUrls) .filter(url -> url.endsWith(".cz")) .flatMap(url -> Observable.just(getHttpStatus(url))) .take(5) .subscribe(s -> Timber.i(String.valueOf(s)));
  36. 36. Threading
  37. 37. Threading ▪ subscribeOn(Scheduler) ▪ subscribes to Observable on the Scheduler ▪ observeOn(Scheduler) ▪ Observable emits on the Scheduler
  38. 38. Schedulers
  39. 39. Schedulers ▪ computation() ▪ io() ▪ newThread() ▪ from(Executor)
  40. 40. computation() vs. io() ▪ io() – unbounded thread pool – I/O operations ▪ computation() – bounded thread pool – CPU intensive computational work
  41. 41. AndroidSchedulers ▪ mainThread() ▪ from(Looper)
  42. 42. subscribeOn() ▪ subscribeOn() – multiple calls useless – only the first call works! – for all operators
  43. 43. observeOn() ▪ can be called multiple times
  44. 44. Threading ▪ operators have default Schedulers ▪ check Javadoc for it
  45. 45. Threading ▪ range() – no preference, uses current thread ▪ interval() – computation() thread
  46. 46. Threading ▪ just() – current thread ▪ delay(long, TimeUnit) – computation() thread
  47. 47. Observable methods ▪ extra actions ▪ doOnNext() ▪ doOnError() ▪ doOnSubscribe() ▪ doOnUnsubscribe() ▪ …
  48. 48. Async composition apiEndpoint.login()
  49. 49. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken))
  50. 50. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser())
  51. 51. Async composition apiEndpoint.login() .doOnNext(accessToken -> storeCredentials(accessToken)) .flatMap(accessToken -> serviceEndpoint.getUser()) .flatMap(user -> serviceEndpoint.getUserContact(user.getId()))
  52. 52. Subscription
  53. 53. Subscription Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() );
  54. 54. Subscription Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() ); s.unsubscribe();
  55. 55. Subscription Subscription s = mAppInfoProvider.getAppsObservable() .subscribe( appInfo -> Timber.i(appInfo.getPackageName() ); s.unsubscribe(); Timber.i("unsubscribed: " + s.isUnsubscribed());
  56. 56. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription);
  57. 57. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription); compositeSubscription.unsubscribe();
  58. 58. CompositeSubscription CompositeSubscription compositeSubscription = new CompositeSubscription(); compositeSubscription.add(subscription); compositeSubscription.unsubscribe(); compositeSubscription.clear();
  59. 59. Bridging non-Rx APIs
  60. 60. Bridging non-Rx APIs ▪ just() private Object getData() {...} public Observable<Object> getObservable() { return Observable.just(getData()); }
  61. 61. Tip #2 - defer() ▪ use defer() to avoid immediate execution private Object getData() {...} public Observable<Object> getObservable() { return Observable.defer( () -> Observable.just(getData()) ); }
  62. 62. Subjects
  63. 63. Subjects ▪ Observable & Observer ▪ bridge between non-Rx API ▪ stateful – terminal state ▪ don’t pass data after onComplete()/onError()
  64. 64. Subjects ▪ cannot be reused – always create a new instance when subscribing to an Observable
  65. 65. Subjects ▪ AsyncSubject ▪ BehaviorSubject ▪ ReplaySubject ▪ PublishSubject ▪ SerializedSubject
  66. 66. Subjects Subject subject = … subject.subscribe(subscriber); subject.onNext(A); subject.onNext(B);
  67. 67. AsyncSubject ▪ last item, after Observable completes
  68. 68. BehaviorSubject ▪ emits most recent and all subsequent items
  69. 69. PublishSubject ▪ emits only subsequent items
  70. 70. ReplaySubject ▪ emit all the items
  71. 71. RxRelay
  72. 72. RxRelay ▪ https://github.com/JakeWharton/RxRelay ▪ Relay = Observable & Action1 ▪ call() ▪ Relay = Subject - onComplete() - onError()
  73. 73. RxRelay ▪ Subject ▪ stateful ▪ Relay ▪ stateless
  74. 74. RxRelay Relay relay = … relay.subscribe(observer); relay.call(A); relay.call(B);
  75. 75. RxRelay ▪ BehaviorRelay ▪ PublishRelay ▪ ReplayRelay ▪ SerializedRelay
  76. 76. Android Lifecycle ▪ problems ▪ continuing Subscription during configuration change ▪ memory leaks
  77. 77. Android Lifecycle ▪ continuing Subscription during configuration change ▪ cache() ▪ replay()
  78. 78. Android Lifecycle ▪ memory leaks ▪ bind to Activity/Fragment lifecycle ▪ RxLifecycle
  79. 79. RxLifecycle
  80. 80. RxLifecycle ▪ auto unsubscribe based on Activity/Fragment lifecycle myObservable .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY)) .subscribe(); myObservable .compose(RxLifecycle.bindActivity(lifecycle)) .subscribe();
  81. 81. RxLifecycle ▪ obtain ActivityEvent or FragmentEvent by: A. rxlifecycle-components + subclass RxActivity, RxFragment B. Navi + rxlifecycle-navi C. Write it yourself
  82. 82. RxLifecycle public class MyActivity extends RxActivity { @Override public void onResume() { super.onResume(); myObservable .compose(bindToLifecycle()) .subscribe(); } }
  83. 83. RxLifecycle public class MyActivity extends NaviActivity { private final ActivityLifecycleProvider provider = NaviLifecycle.createActivityLifecycleProvider(this); @Override public void onResume() { super.onResume(); myObservable .compose(provider.bindToLifecycle()) .subscribe(…); } }
  84. 84. Navi naviComponent.addListener(Event.CREATE, new Listener<Bundle>() { @Override public void call(Bundle bundle) { setContentView(R.layout.main); } } );
  85. 85. RxNavi RxNavi.observe(naviComponent, Event.CREATE) .subscribe(bundle -> setContentView(R.layout.main));
  86. 86. RxLifecycle public class MainActivityFragment extends Fragment { BehaviorSubject<FragmentEvent> mLifecycleSubject = BehaviorSubject.create();
  87. 87. 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(…); } }
  88. 88. 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); } }
  89. 89. RxBinding
  90. 90. RxBinding ▪ binding for UI widgets
  91. 91. RxBinding RxView.clicks(vBtnSearch) .subscribe( v -> { Intent intent = new Intent(getActivity(), SearchActivity.class); startActivity(intent); } );
  92. 92. RxBinding RxTextView.textChanges(vEtSearch) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );
  93. 93. RxBinding RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );
  94. 94. RxBinding RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );
  95. 95. 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'
  96. 96. Retrofit
  97. 97. Retrofit ▪ sync or async API @GET("group/{id}/users") Call<List<User>> groupList(@Path("id") int groupId);
  98. 98. Retrofit ▪ reactive API @GET("group/{id}/users") Observable<List<User>> groupList(@Path("id") int groupId);
  99. 99. Retrofit – onNext() with Response, then onComplete() – onError() in case of error @GET("/data") Observable<Response> getData( @Body DataRequest dataRequest);
  100. 100. Retrofit RxTextView.textChanges(vEtSearch) .debounce(2, TimeUnit.SECONDS) .observeOn(Schedulers.io()) .flatMap(s -> mApiService.search(s.toString())) .subscribe( response -> Timber.i("Count: " + response.totalCount()) );
  101. 101. SQLBrite
  102. 102. THE END

×