Recipes in RxJava for Android, v.3

Recipes in RxJava for Android, v.3

Some real-life Rx recipes for Android - how to create Observables, when to use Subjects, how to control the observer lifecycle with Subscriptions, how to control multithreading etc. Presented at Droidcon Berlin, June 2016.

There was no video, but you can listen to the audio here: https://voicerepublic.com/talks/recipes-in-rxjava-for-android

Also, there's a video of a presentation from a slightly different set of slides:
https://www.youtube.com/watch?v=JrWTMqd_UUo

6f45238394f11243126f8719a5147fc1?s=128

Sasa Sekulic

June 17, 2016
Tweet

Transcript

  1. 3FDJQFTJO3Y+BWBGPS "OESPJE Sasa Sekulic co-author of the Manning book “Grokking

    Rx”(with Fabrizio Chignoli and Ivan Morgillo); developer of the UN-WFP ShareTheMeal app; cofounder of Alter Ego Solutions @sasa_sekulic | www.alterego.solutions | Grokking Rx MEAP: http://bit.ly/grokking-rx
  2. 6/8PSME'PPE1SPHSBNNF 4IBSF5IF.FBM We provide food to schoolchildren in need –

    over 5.000.000 meals shared! It costs only €0.40 to feed one child for a day. www.sharethemeal.org Google Play Best of 2015 & Editor's Choice, SXSW 2015 Innovation Award 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  3. #BTJDTPG3Y+BWB  Observer  Observable  Subscription  Subscriber (Observer

    & Subscription)  Subject (Observer & Observable) 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  4. 0CTFSWBCMFDSFBUJPOGSPNBOZPCKFDU  just(), from() – simple, executed immediately upon creation

     create() – executed upon subscription but need to take care of contract calls  defer()/fromCallable() – simple, but executed upon subscription 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  5. 0CTFSWBCMFDSFBUJPOmFYBNQMFUPPTJNQMF  just(), from() – simple, executed immediately upon creation

    public Observable<Boolean> exampleBlocking() { SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE); return Observable.just(prefs.getBoolean("boolean", false)); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  6. 0CTFSWBCMFDSFBUJPOmFYBNQMFUPPDPNQMJDBUFE  create() – executed upon subscription but need to

    take care of contract calls public Observable<Boolean> exampleTooComplicated() { SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE); return Observable.create(new Observable.OnSubscribe<Boolean>() { @Override public void call(Subscriber<? super Boolean> subscriber) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(prefs.getBoolean("boolean", false)); if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } } }); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  7. 0CTFSWBCMFDSFBUJPOmDSFBUF JTOUBTFBTZBTJU MPPLT  think about the unsubscription/backpressure chain Observable.create(s

    -> { int i = 0; while (true) { s.onNext(i++); } }).subscribe(System.out::println);  for list of many more other problems: http://akarnokd.blogspot.hu/2015/05/pitfalls-of-operator- implementations.html 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  8. 0CTFSWBCMFDSFBUJPOmFYBNQMFKVTUSJHIU  defer() – simple, but executed upon subscription 

    fromCallable() – the same, for methods that return values public Observable<Boolean> exampleJustRight() { SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE); return Observable.defer(new Func0<Observable<Boolean>>() { @Override public Observable<Boolean> call() { return Observable.just(prefs.getBoolean("boolean", false)); } }); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  9. 0CTFSWBCMFDSFBUJPOmTQFDJBMDBTFPGGSPN  from(Future<? extends T>) – blocking, cannot unsubscribe (but

    you can specify timeout or Scheduler)  from(Iterable<? extends T>), from(T[]) – convert Iterable/array event into events from Iterable/array List<Boolean> list = new ArrayList<>(); Observable.just(list).subscribe(new Observer<List<Boolean>>(){ ... } //takes List<Boolean>, emits List<Boolean> Observable.from(list).subscribe(new Observer<Boolean>(){ ... } //takes List<Boolean>, emits Boolean items from the list 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  10. 0CTFSWBCMFDSFBUJPOmGPS&BDI FRVJWBMFOUPQT List<Boolean> list = new ArrayList<>(); Observable.from(list) .map(aBoolean ->

    !aBoolean) //returns value .flatMap(aBoolean -> Observable.just(aBoolean.hashCode()) //returns observable<value> .toList() //or .toSortedList() .subscribe(new Observer<List<Integer>>() { ... 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  11. 4VCKFDUT  they're Hot observables – the values are generated

    independently of existence of subscribers  use them to send values to Observers outside creation block  enable you to have communication between the Observer and Observable-value generator  access onNext(), onError() and onCompleted() whenever you want  single-threaded by default, don't mix threads when calling onXXX()!  for thread safety, wrap them in a SerializedSubject() or call Subject.toSerialized().onXXX() 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  12. 4VCKFDUT UZQFT  PublishSubject – doesn’t store any states, just

    sends what it receives while subscribed (you miss anything in between) – epitome of Hot observable  BehaviourSubject – remembers the last value – emptied after onCompleted()/onError() - can be initialized with a value 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  13. 4VCKFDUTm UZQFT DPOUE  AsyncSubject – remembers the last value

    – after onCompleted() subscribers receive the last value and the onCompleted() event – after onError() subscribers receive just the onError() event  ReplaySubject – remembers everything but can be limited (create(int bufferCapacity), createWithSize(int size), createWithTime(long time, TimeUnit unit, final Scheduler scheduler), createWithTimeAndSize(long time, TimeUnit unit, int size, final Scheduler scheduler)) 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  14. 4VCKFDUTm NVTJDQMBZFSFYBNQMF69 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

  15. 4VCKFDUTm NVTJDQMBZFSFYBNQMFTUBUFT 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

  16. 4VCKFDUTm NVTJDQMBZFSFYBNQMF0CTFSWFS public class MusicPlayer { public enum PLAYER_STATE {STOPPED,

    PLAYING} Observer<PLAYER_STATE> playerObserver = new Observer<PLAYER_STATE>() { @Override public void onNext(PLAYER_STATE state) { currentPlayerState = state; if (state == PLAYER_STATE.PLAYING) { startSong(); showPauseButton(); } else if (state == PLAYER_STATE.STOPPED) { stopSong(); showPlayButton(); } } ... }; } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  17. 4VCKFDUTm NVTJDQMBZFSFYBNQMF4VCKFDU public class MusicPlayer { PLAYER_STATE currentPlayerState = PLAYER_STATE.STOPPED;

    BehaviorSubject<PLAYER_STATE> playerStateSubject = BehaviorSubject.create(currentPlayerState); public MusicPlayer() { playerStateSubject.subscribe(playerObserver); } private void pressButton() { if (currentPlayerState == PLAYER_STATE.PLAYING) { playerStateSubject.onNext(PLAYER_STATE.STOPPED); } else if (currentPlayerState == PLAYER_STATE.STOPPED) { playerStateSubject.onNext(PLAYER_STATE.PLAYING); } } } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  18. 4VCKFDUTm NVTJDQMBZFSFYBNQMFPQUJNJ[BUJPO  We don't need currentPlayerState, just use playerStateSubject.getValue()

     Don't expose Subject to others: when offering them for subscribing, use getObservable() public Observable<PLAYER_STATE> getPlayerStateObservable() { return playerStateSubject.asObservable(); }  Use Subscription to control Observer lifecycle! 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  19. 4VCTDSJQUJPO  Subscription is a relationship between the Observer and

    the Observable public MusicPlayer() { Subscription stateSub = playerStateSubject.subscribe(playerObserver); }  Subscription is returned by the subscribe() public MusicPlayer() { Subscription stateSub = playerStateSubject //Observable .filter(state -> state == PLAYING) //Observable .map(state -> someMethod()) //Observable .subscribe(playerObserver); //Subscription }  Very simple: isUnsubscribed(), unsubscribe() 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  20. 4VCTDSJQUJPOm VTBHF  To control the lifecycle and/or release the

    resources, call it in onPause()/onStop()/onDestroy(): @Override protected void onDestroy() { super.onDestroy(); if (mPlayerSubscription != null && !mPlayerSubscription.isUnsubscribed()) { mPlayerSubscription.unsubscribe(); } } If you don't want to do null checks, initialize the subscription: Subscription mPlayerSubscription = Subscriptions.unsubscribed(); //or Subscriptions.empty(); 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  21. 4VCTDSJQUJPOm VTBHF DPOUE To prevent multiple executions/subscriptions: public MusicPlayer() {

    if (mPlayerSubscription == null || mPlayerSubscription.isUnsubscribed()) { mPlayerSubscription = playerStateSubject.subscribe(playerObserver); } }  Important: unsubscribing has no impact on the observable or other subscriptions! 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  22. $PNQPTJUF4VCTDSJQUJPOm HSPVQJOH4VCTDSJQUJPOT When you need to control multiple subscriptions at

    the same time: CompositeSubscription activitySubscriptions = new CompositeSubscription(); @Override protected void onCreate() { mPlayerSub = playerStateSubject.subscribe(playerObserver); mPlayerSub2 = playerStateSubject.subscribe(playerObserver2); activitySubscriptions = new CompositeSubscriptions(mPlayerSub, mPlayerSub2); } @Override protected void onResume() { mPlayerSub3 = playerStateSubject.subscribe(playerObserver3); activitySubscriptions.add(mPlayerSub3); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  23. $PNQPTJUF4VCTDSJQUJPOm HSPVQJOH4VCTDSJQUJPOT DPOUE When you need to control multiple subscriptions

    at the same time: removing with remove(), unsubscribing all with unsubscribe(), and clear() unsubscribes all and removes them from the CompositeSubscription: @Override protected void onPause() { mPlayerSub3 = playerStateSubject.subscribe(playerObserver3); activitySubscriptions.remove(mPlayerSub3); } @Override protected void onDestroy() { super.onDestroy(); if (activitySubscriptions.hasSubscriptions() && !activitySubscriptions.isUnsubscribed()) { activitySubscriptions.unsubscribe(); } } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  24. 4VCTDSJQUJPOmDPOUSPMUIFMJGFDZDMF  Manually, on the Activity or Fragment level –

    create when you want, unsubscribe in onPause()/onStop()/onDestroy()  Automatically, on the Activity or Fragment level – use https://github.com/trello/RxLifecycle public class MainActivity extends RxAppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { Observable.interval(1, TimeUnit.SECONDS) .compose(this.<Long>bindUntilEvent(ActivityEvent.PAUSE)) //binds to onPause() .subscribe(onCreateObserver); } } .compose(this.<Long>bindUntilEvent(ActivityEvent.DESTROY)) //binds to onDestroy() .compose(this.<Long>bindToLifecycle()) //binds automatically to opposite method (if called from onResume() it will be bound to onPause() One very important note: RxLifecycle doesn't call unsubscribe() but calls onCompleted() instead! 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  25. 4VCTDSJQUJPOm DPOUSPMUIFMJGFDZDMF HMPCBM  Manually on the global, Application level

    – create when you want, and unsubscribe? Never, so do the transactions atomically, close the observables and unsubscribe from activities/fragments: public class SplashActivity extends AppCompatActivity { Subscription mUserSubscription = Subscriptions.empty(); @Override protected void onCreate(Bundle savedInstanceState) { mUserManager.getUserObservable() .subscribe(user -> continue(), throwable -> showError(throwable)); } @Override protected void onDestroy() { if (!mUserSubscription.isUnsubscribed()) mUserSubscription.unsubscribe(); } } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  26. 4VCTDSJQUJPOm DPOUSPMUIFMJGFDZDMF HMPCBM DPOUE public UserManager () { //from() will

    automatically call onCompleted() so no pending references Observable.from(loadUserFromSavedPreferences()) .subscribe(user -> mUserSubject.onNext(user), throwable -> mUserSubject.onError(throwable)); } Observable<User> getUserObservable() { return mUserSubject.asObservable(); } }  There's no automated way to manage lifecycle on the global level! 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  27. "SDIJUFDUVSFFYBNQMF CPVOEUP"QQDPOUFYU 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

  28. .BOBHFSFYBNQMF CPVOEUP"QQDPOUFYU loads a configuration, then loads a saved user,

    and propagates it: public UserManager () { public UserManager() { mConfigurationManager.getCurrentConfigObservable() .flatMap(config -> loadUserFromSavedPreferences()) .subscribe(user -> mUserSubject.onNext(user), throwable -> mUserSubject.onError(throwable)); } Observable<User> getUserObservable() { return mUserSubject.asObservable(); } } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  29. .BOBHFSFYBNQMF FSSPSIBOEMJOH  sending onError() closes the subscription and no

    more events are sent!  onError() handles ALL errors in the chain!  to prevent this, use error handling methods: – onErrorReturn() - use other value – onErrorResumeNext() - use other observable – onExceptionResumeNext() - handles Exceptions but not Throwables mConfigurationManager.getCurrentConfigObservable() .onErrorReturn(config -> Config.Empty) .flatMap(config -> loadUserFromSavedPreferences()) .onErrorReturn(user -> User.Empty) .subscribe(user -> mUserSubject.onNext(user); 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  30. .BOBHFSFYBNQMF TQMJUUJOHTVCTDSJQUJPOT  the whole of the chain is an

    Observable until the subscribe()  if you don’t use error management (onErrorReturn()/onErrorResumeNext()…), all subscriptions have to have onError()– but that is always a good idea!!! Observable<Config) configObservable = mConfigurationManager.getCurrentConfigObservable() .onErrorReturn(config -> Config.Empty); Subscription configValidSub = configObservable .filter(config -> config != null && config != Config.Empty) .flatMap(config -> loadUserFromSavedPreferences()) .onErrorReturn(user -> User.Empty) .subscribe(user -> mUserSubject.onNext(user)); Subscription configInvalidSub = configObservable .filter(config -> config == null || config == Config.Empty) .subscribe(config -> Log.d("App", "config empty!"); 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  31. .BOBHFSFYBNQMF SFVTJOHPCTFSWBCMFDIBJOT  if you have a part of the

    chain that repeats itself – extract it in a Transformer<T, R> () Subscription configValidUserValidSub = configObservable .filter(config -> config != null && config != Config.Empty) .flatMap(config -> loadUserFromSavedPreferences()) .onErrorReturn(user -> User.Empty) .filter(user -> user != User.Empty) .subscribe(user -> mUserSubject.onNext(user)); Subscription configValidUserInalidSub = configObservable .filter(config -> config != null && config != Config.Empty) .flatMap(config -> loadUserFromSavedPreferences()) .onErrorReturn(user -> User.Empty) .filter(user -> user == User.Empty) .subscribe(user -> Log.d("APP", "user empty!"); 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  32. .BOBHFSFYBNQMF SFVTJOHPCTFSWBCMFDIBJOT DPOUE Observable.Transformer<Config, User> convertConfigToUser() { return new Observable.Transformer<Config,

    User>() { @Override public Observable<User> call(Observable<Config> configObservable) { return configObservable .filter(config -> config != null && config != Config.Empty) .flatMap(config -> loadUserFromSavedPreferences()) .onErrorReturn(user -> User.Empty); } }; } Subscription configValidUserValidSub = configObservable .compose(convertConfigToUser()) .filter(user -> user != User.Empty) .subscribe(user -> mUserSubject.onNext(user)); Subscription configValidUserInalidSub = configObservable .compose(convertConfigToUser()) .filter(user -> user == User.Empty) .subscribe(user -> Log.d("APP", "user empty!"); 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  33. .BOBHFSFYBNQMF NVMUJUISFBEJOH  RxJava is asynchronous by default, but not

    multithreaded by default!  use explicit methods to change the thread:  subscribeOn() – it changes the upstream thread (until that point)  observeOn() – it changes the downstream thread (after that point) Observable.Transformer<Config, User> convertConfigToUser() { return new Observable.Transformer<Config, User>() { @Override public Observable<User> call(Observable<Config> configObservable) { return configObservable .filter(config -> config != null && config != Config.Empty) .flatMap(config -> loadUserFromSavedPreferences()) .onErrorReturn(user -> User.Empty) .subscribeOn(Schedulers.io()) //everything up to this uses io() thread .observeOn(AndroidSchedulers.mainThread()); // everything after this // uses UI thread } }; } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  34. .VMUJUISFBEJOHm TVCTDSJCF0OWTPCTFSWF0O  subscribeOn() – it changes the upstream thread

    (until that point)  observeOn() – it changes the downstream thread (after that point) Observable.fromCallable(()-> doSomething1(); //executed on IO thread //if there's no subscribeOn(), execs on calling thread ) .subscribeOn(Schedulers.io()) .filter(something -> doSomething2()) //subscribe call AND everything up to this uses io() thread .observeOn(Schedulers.computation()); //changes thread to computation! .flatMap(something -> doSomething3()) .onErrorReturn(user -> User.Empty) .subscribeOn(Schedulers.computation()) //doesn't matter!!! wasteful! .observeOn(AndroidSchedulers.mainThread()) //switches to UI thread .subscribe(result -> doSomething4()); 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  35. .BOBHFSFYBNQMF NVMUJUISFBEJOH DPOUE  RxJava Scheduler:  computation() – bound

    (limited by CPU cores)  io() - unbound  immediate() – executes immediately on current thread  trampoline() – executes after finishing, on current thread  newThread() – creates new thread  from(Executor) – use custom executor  test() – uses TestScheduler with manual scheduling controls  rxandroid library: AndroidScheduler.mainThread()  Multithreading is hard, RxJava makes it easier – but it's still not easy!  do not specify the thread unless you really need to 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD
  36. 'VSUIFSSFBEJOHXBUDIJOH  https://github.com/ReactiveX/RxJava  RxJava Single discussion: https://github.com/ReactiveX/RxJava/issues/1594  RxJava

    Subscriber discussion: https://github.com/ReactiveX/RxJava/issues/792  New viewbinding library using RxJava: https://github.com/alter-ego/androidbound  ReactiveX homepage: http://reactivex.io/  Interactive marble diagrams for operators: http://www.rxmarbles.com/  Ben Christensen's talks: https://speakerdeck.com/benjchristensen/  David Karnok's Advanced RxJava blog: http://akarnokd.blogspot.hu/  Grokking Rx MEAP: http://bit.ly/grokking-rx 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD