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

Recipes in RxJava for Android, v.2

Recipes in RxJava for Android, v.2

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 Croatia, Zagreb, 2016.

There was no video, but here's a video of a presentation from a slightly different set of slides:
https://www.youtube.com/watch?v=JrWTMqd_UUo

Sasa Sekulic

April 28, 2016
Tweet

More Decks by Sasa Sekulic

Other Decks in Programming

Transcript

  1. Recipes in RxJava for
    Android
    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

    View full-size slide

  2. UN World Food Programme - ShareTheMeal
    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, SXSW 2015 Innovation Award for New Economy
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  3. Basics of RxJava
     Observer
     Observable
     Subscription
     Subscriber (Observer & Subscription)
     Subject (Observer & Observable)
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  4. Observable creation from any object
     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
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  5. Observable creation –example 1: too simple
     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));
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  6. Observable creation –example 2: too complicated
     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 super Boolean> subscriber) {
    if (subscriber.isUnsubscribed()) {
    return;
    }
    subscriber.onNext(prefs.getBoolean("boolean", false));
    if (!subscriber.isUnsubscribed()) {
    subscriber.onCompleted();
    }
    }
    });
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  7. Observable creation –create() isn't as easy as it
    looks
     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
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  8. Observable creation –example 3: just right
     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));
    }
    });
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  9. Observable creation –special case of from()
     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 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
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  10. Observable creation –forEach() equivalent ops
    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>() { ...
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  11. Subjects
     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()
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  12. Subjects - types
     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
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  13. Subjects – types, cont'd
     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))
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  14. Subjects – music player example UX
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  15. Subjects – music player example states
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  16. Subjects – music player example Observer
    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();
    }
    }
    ...
    };
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  17. Subjects – music player example Subject
    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);
    }
    }
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  18. Subjects – music player example optimization
     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!
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  19. Subscription
     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()
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  20. Subscription – usage
     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();
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  21. Subscription – usage, cont'd
    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!
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  22. CompositeSubscription – grouping Subscriptions
    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);
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  23. CompositeSubscription – grouping Subscriptions
    (cont'd)
    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();
    }
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  24. Subscription –control the lifecycle
     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!
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  25. Subscription – control the lifecycle (global)
     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();
    }
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  26. Subscription – control the lifecycle (global, cont'd)
    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!
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  27. Architecture example (bound to App context)
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  28. Manager example (bound to App context)
    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();
    }
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  29. Manager example - error handling
     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);
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  30. Manager example - splitting subscriptions
     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!!!
    ObservablemConfigurationManager.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!");
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  31. Manager example - reusing observable chains
     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!");
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  32. Manager example - reusing observable chains
    (cont'd)
    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!");
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  33. Manager example - multithreading
     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
    }
    };
    }
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  34. Multithreading – subscribeOn vs observeOn
     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());
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  35. Manager example - multithreading (cont'd)
     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
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide

  36. Further reading/watching
     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
     50% discount coupon until May 2: mlmorgillo
    Recipes in RxJava for Android | #GrokkingRx | @sasa_sekulic

    View full-size slide