Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

#BTJDTPG3Y+BWB  Observer  Observable  Subscription  Subscriber (Observer & Subscription)  Subject (Observer & Observable) 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

0CTFSWBCMFDSFBUJPOmFYBNQMFUPPTJNQMF  just(), from() – simple, executed immediately upon creation public Observable exampleBlocking() { SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE); return Observable.just(prefs.getBoolean("boolean", false)); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 6

Slide 6 text

0CTFSWBCMFDSFBUJPOmFYBNQMFUPPDPNQMJDBUFE  create() – executed upon subscription but need to take care of contract calls public Observable exampleTooComplicated() { SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE); return Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(prefs.getBoolean("boolean", false)); if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } } }); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

0CTFSWBCMFDSFBUJPOmFYBNQMFKVTUSJHIU  defer() – simple, but executed upon subscription  fromCallable() – the same, for methods that return values public Observable exampleJustRight() { SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE); return Observable.defer(new Func0>() { @Override public Observable call() { return Observable.just(prefs.getBoolean("boolean", false)); } }); } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 9

Slide 9 text

0CTFSWBCMFDSFBUJPOmTQFDJBMDBTFPGGSPN  from(Future) – blocking, cannot unsubscribe (but you can specify timeout or Scheduler)  from(Iterable), from(T[]) – convert Iterable/array event into events from Iterable/array List list = new ArrayList<>(); Observable.just(list).subscribe(new Observer>(){ ... } //takes List, emits List Observable.from(list).subscribe(new Observer(){ ... } //takes List, emits Boolean items from the list 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 10

Slide 10 text

0CTFSWBCMFDSFBUJPOmGPS&BDI FRVJWBMFOUPQT List list = new ArrayList<>(); Observable.from(list) .map(aBoolean -> !aBoolean) //returns value .flatMap(aBoolean -> Observable.just(aBoolean.hashCode()) //returns observable .toList() //or .toSortedList() .subscribe(new Observer>() { ... 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

4VCKFDUTm NVTJDQMBZFSFYBNQMF69 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

4VCKFDUTm NVTJDQMBZFSFYBNQMF0CTFSWFS public class MusicPlayer { public enum PLAYER_STATE {STOPPED, PLAYING} Observer playerObserver = new Observer() { @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

Slide 17

Slide 17 text

4VCKFDUTm NVTJDQMBZFSFYBNQMF4VCKFDU public class MusicPlayer { PLAYER_STATE currentPlayerState = PLAYER_STATE.STOPPED; BehaviorSubject 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

Slide 18

Slide 18 text

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 getPlayerStateObservable() { return playerStateSubject.asObservable(); }  Use Subscription to control Observer lifecycle! 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

$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

Slide 23

Slide 23 text

$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

Slide 24

Slide 24 text

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.bindUntilEvent(ActivityEvent.PAUSE)) //binds to onPause() .subscribe(onCreateObserver); } } .compose(this.bindUntilEvent(ActivityEvent.DESTROY)) //binds to onDestroy() .compose(this.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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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 getUserObservable() { return mUserSubject.asObservable(); } }  There's no automated way to manage lifecycle on the global level! 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 27

Slide 27 text

"SDIJUFDUVSFFYBNQMF CPVOEUP"QQDPOUFYU 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 28

Slide 28 text

.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 getUserObservable() { return mUserSubject.asObservable(); } } 3FDJQFTJO3Y+BWBGPS"OESPJE](SPLLJOH3Y]!TBTB@TFLVMJD

Slide 29

Slide 29 text

.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

Slide 30

Slide 30 text

.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.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

Slide 31

Slide 31 text

.BOBHFSFYBNQMF SFVTJOHPCTFSWBCMFDIBJOT  if you have a part of the chain that repeats itself – extract it in a Transformer () 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

Slide 32

Slide 32 text

.BOBHFSFYBNQMF SFVTJOHPCTFSWBCMFDIBJOT DPOUE Observable.Transformer convertConfigToUser() { return new Observable.Transformer() { @Override public Observable call(Observable 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

Slide 33

Slide 33 text

.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 convertConfigToUser() { return new Observable.Transformer() { @Override public Observable call(Observable 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

Slide 34

Slide 34 text

.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

Slide 35

Slide 35 text

.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

Slide 36

Slide 36 text

'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