$30 off During Our Annual Pro Sale. View Details »

Common RxJava Mistakes

Common RxJava Mistakes

Talk given at Droidcon SF 2016.

Recording here: https://www.youtube.com/watch?v=QdmkXL7XikQ

Daniel Lew

March 17, 2016
Tweet

More Decks by Daniel Lew

Other Decks in Programming

Transcript

  1. Common RxJava Mistakes
    Dan Lew

    View Slide

  2. Lambda Notation
    new Func1() {

    @Override public Integer call(String s) {

    return s.length();

    }

    };
    s -> s.length();

    View Slide

  3. Lambda Notation
    new Func1() {

    @Override public Integer call(String s) {

    return s.length();

    }

    };
    s -> {

    return s.length();

    }
    s -> s.length()

    View Slide

  4. Lambda Notation
    new Func1() {

    @Override public Integer call(String s) {

    return s.length();

    }

    };
    (String s) -> {

    return s.length();

    }
    s -> s.length()
    …is the same as…

    View Slide

  5. Lambda Notation
    new Func1() {

    @Override public Integer call(String s) {

    return s.length();

    }

    };
    (String s) -> {

    return s.length();

    }
    s -> s.length()
    …is the same as…
    …is the same as…

    View Slide

  6. The Stream

    View Slide

  7. Observable obs = Observable.just("Hello!");

    obs.map(s -> s.length());

    obs.subscribe(i -> System.out.println(i));
    Observable.just("Hello!")

    .map(s -> s.length())

    .subscribe(i -> System.out.println(i));
    Versus
    DOES NOT WORK

    View Slide

  8. Observable step1 = Observable.just("Hello!");

    Observable step2 = step1.map(s -> s.length());

    step2.subscribe(i -> System.out.println(i));

    View Slide

  9. Observable step1 = Observable.just("Hello!");

    Observable step2 = step1.map(s -> s.length());

    step2.subscribe(i -> System.out.println(i));

    View Slide

  10. Observable step1 = Observable.just("Hello!");

    Observable step2 = step1.map(s -> s.length());

    step2.subscribe(i -> System.out.println(i));

    View Slide

  11. Upstream
    Downstream

    View Slide

  12. Creating Observables 101
    • just(object)
    • from(iterable)
    • Whatever Retrofit is doing
    • create()
    NO

    View Slide

  13. Difficulties
    • Observable contract
    • onNext* -> onCompleted | onError
    • Unsubscription
    • Backpressure

    View Slide

  14. class MyClass {

    String value;


    Observable value() {

    return Observable.create(subscriber -> {

    subscriber.onNext(value);

    subscriber.onCompleted();

    });

    }

    }
    ???

    View Slide

  15. class MyClass {

    String value;


    Observable value() {

    return Observable.create(subscriber -> {

    subscriber.onNext(value);

    subscriber.onCompleted();

    });

    }

    }
    BAD

    View Slide

  16. class MyClass {

    String value;


    Observable value() {

    return Observable.create(subscriber -> {

    subscriber.onNext(value);

    subscriber.onCompleted();

    });

    }

    }
    return Observable.fromCallable(() -> value);

    View Slide

  17. class MyClass {

    String value;


    Observable value() {

    return Observable.create(subscriber -> {

    subscriber.onNext(value);

    subscriber.onCompleted();

    });

    }

    }
    return Observable.defer(() -> Observable.just(value));

    View Slide

  18. class MyClass {

    BehaviorSubject value;


    Observable value() {

    return value.asObservable();

    }


    void setValue(String s) {

    value.onNext(s);

    }

    }

    View Slide

  19. RxRelay
    • Safer Subjects
    • https://github.com/JakeWharton/RxRelay

    View Slide

  20. Lessons
    • Learn your creation methods!
    • Resort to create() when appropriate
    • (which is almost never)

    View Slide

  21. Operator
    Misunderstandings

    View Slide

  22. map vs. flatMap

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. Types
    • map: T -> R
    • flatMap: T -> Observable

    View Slide

  28. Exercise
    • Find an API with search
    • Query search results via Observable
    • Request extra data on results via Observable
    • Use only one subscribe()

    View Slide

  29. flatMap vs. concatMap
    • Same, except…
    • #ordermatters
    • flatMap -> Unordered merge
    • concatMap -> Ordered merge

    View Slide

  30. View Slide

  31. toList()
    • Item(s) -> List
    • “Why does nothing happen?!”
    • Missing onCompleted

    View Slide

  32. Versus
    Key!

    View Slide

  33. List of Lists
    Observable> obs = /* ...etc... */;


    obs.flatMap(list -> Observable.from(list))

    .map(/* ...something... */)

    .toList();


    obs.flatMap(

    list -> Observable.from(list)

    .map(/* ...something... */)

    .toList()

    );

    View Slide

  34. List of Lists
    Observable> obs = /* ...etc... */;


    obs.flatMap(list -> Observable.from(list))

    .map(/* ...something... */)

    .toList();


    obs.flatMap(

    list -> Observable.from(list)

    .map(/* ...something... */)

    .toList()

    );
    result: 1 list

    View Slide

  35. List of Lists
    Observable> obs = /* ...etc... */;


    obs.flatMap(list -> Observable.from(list))

    .map(/* ...something... */)

    .toList();


    obs.flatMap(

    list -> Observable.from(list)

    .map(/* ...something... */)

    .toList()

    );
    result: 1 list
    result: N lists

    View Slide

  36. take(1) vs. first()
    • take(1) - emits once, or not at all
    Observable.empty().take(1)
    • first() - emits once, or crashes
    Observable.empty().first()
    crash!
    emits nothing

    View Slide

  37. cache() vs replay()
    • Don’t use cache()
    • Use replay()
    • cache() == replay().autoConnect()

    View Slide

  38. Custom Operators

    View Slide

  39. Operators I Have Written
    • OperatorNullToDefault
    • OperatorSortLists
    • OperatorSubscribeUntil
    • ContinueWithIfEmptyOperator

    View Slide

  40. Operators I Should Have
    Written

    View Slide

  41. Why not?
    • Each operator —> different flow
    • Framework handles common flows
    • Resort to custom operators for unique flows
    • Operators are hard!

    View Slide

  42. • OperatorNullToDefault
    • OperatorSortLists
    • OperatorSubscribeUntil
    • ContinueWithIfEmptyOperator
    map()
    switchIfEmpty()
    takeUntil()

    View Slide

  43. Threading

    View Slide

  44. Observable.range(0, 5)

    .subscribe();
    Observable.interval(1, TimeUnit.SECONDS)

    .subscribe();
    current thread
    computation()

    View Slide

  45. Observable.just("Hello")

    .subscribe();
    Observable.just("Hello")

    .delay(1, TimeUnit.SECONDS)

    .subscribe();
    current thread
    computation()

    View Slide

  46. Observable.just("Hello")

    .subscribeOn(Schedulers.io())

    .map()

    .subscribeOn(Schedulers.computation())

    .subscribe();
    io()

    View Slide

  47. Observable.just("Hello")

    .observeOn(Schedulers.io())

    .map()

    .observeOn(Schedulers.computation())

    .subscribe();
    io()
    computation()
    current thread

    View Slide

  48. View Slide

  49. Lessons
    • Know your scheduler-based operators
    • Use subscribeOn once
    • Use observeOn liberally

    View Slide

  50. Subscriptions

    View Slide

  51. Observer observer = /* some Observer */

    o1.subscribe(observer);

    o2.subscribe(observer);
    Subscriber subscriber = /* some Subscriber */
    o1.subscribe(subscriber);

    o2.subscribe(subscriber);
    Versus

    View Slide

  52. • Observer is an interface
    • Subscriber is a class
    • Observer is stateless
    • Subscriber is stateful

    View Slide

  53. Observer observer = /* some Observer */

    o1.subscribe(observer);

    o2.subscribe(observer);
    Subscriber subscriber = /* some Subscriber */
    o1.subscribe(subscriber);

    o2.subscribe(subscriber);
    Versus
    Can blow up in your face

    View Slide

  54. Memory Leaks
    • Subscriptions can leak!
    • Detect w/ LeakCanary
    • Solution: Unsubscribe

    View Slide

  55. Unsubscribe
    Subscription subscription = Observable.just(1).subscribe();

    subscription.unsubscribe();

    View Slide

  56. CompositeSubscription
    CompositeSubscription cs = new CompositeSubscription();

    cs.add(Observable.interval(1, TimeUnit.SECONDS).subscribe());

    cs.add(Observable.never().subscribe());

    cs.clear();
    use clear()

    View Slide

  57. RxLifecycle
    • Automatic sequence completion
    Observable.interval(1, TimeUnit.SECONDS)

    .compose(bindToLifecycle())

    .subscribe();

    View Slide

  58. Error Handling

    View Slide

  59. onError Uses
    • Unexpected exceptions
    • Unrecoverable streams
    • Flow control
    Blame Retrofit 1

    View Slide

  60. Terminal events suck

    View Slide

  61. onError ≈ try-catch

    View Slide

  62. Exception workarounds
    • Error state
    • onErrorReturn
    • onErrorResumeNext

    View Slide

  63. Debugging onError
    Observable.empty()

    .first()

    .subscribeOn(Schedulers.io())

    .subscribe();
    Crash!

    View Slide

  64. Debugging onError
    Exception in thread "RxCachedThreadScheduler-1" java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread.
    Add `onError` handling.

    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:60)

    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)

    at java.util.concurrent.FutureTask.run(FutureTask.java:266)

    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)

    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)

    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

    at java.lang.Thread.run(Thread.java:745)

    Caused by: rx.exceptions.OnErrorNotImplementedException: Sequence contains no elements

    at rx.Observable$26.onError(Observable.java:7881)

    at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:159)

    at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120)

    at rx.internal.operators.OperatorSubscribeOn$1$1$1.onError(OperatorSubscribeOn.java:71)

    at rx.internal.operators.OperatorSingle$ParentSubscriber.onCompleted(OperatorSingle.java:131)

    at rx.internal.operators.OperatorTake$1.onCompleted(OperatorTake.java:53)

    at rx.Observable$EmptyHolder$1.call(Observable.java:1073)

    at rx.Observable$EmptyHolder$1.call(Observable.java:1070)

    at rx.Observable$2.call(Observable.java:162)

    at rx.Observable$2.call(Observable.java:154)

    at rx.Observable$2.call(Observable.java:162)

    at rx.Observable$2.call(Observable.java:154)

    at rx.Observable.unsafeSubscribe(Observable.java:8098)

    at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62)

    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)

    ... 7 more

    Caused by: java.util.NoSuchElementException: Sequence contains no elements

    ... 18 more
    No references to my code!

    View Slide

  65. Implement onError
    Observable.empty()

    .first()

    .subscribeOn(Schedulers.io())

    .subscribe(o -> System.out.println(o),

    err -> {

    throw new OnErrorNotImplementedException("Source!", err);

    });
    Caused by: rx.exceptions.OnErrorNotImplementedException: Source!

    at net.danlew.experiments.Tester.lambda$main$1(Tester.java:52)

    View Slide

  66. Implement onError
    Observable.empty()

    .first()

    .subscribeOn(Schedulers.io())

    .subscribe(o -> System.out.println(o),

    err -> {

    throw new OnErrorNotImplementedException("Source!", err);

    });
    Caused by: rx.exceptions.OnErrorNotImplementedException: Source!

    at net.danlew.experiments.Tester.lambda$main$1(Tester.java:52)

    View Slide

  67. Implement onError
    Observable.empty()

    .first()

    .subscribeOn(Schedulers.io())

    .subscribe(o -> System.out.println(o),

    err -> {

    throw new OnErrorNotImplementedException("Source!", err);

    });
    Caused by: rx.exceptions.OnErrorNotImplementedException: Source!

    at net.danlew.experiments.Tester.lambda$main$1(Tester.java:52)

    View Slide

  68. Sharing
    • Wanted: Multiple subscribers
    retrofitRequest.subscribe(/* do a thing */);

    retrofitRequest.subscribe(/* do another thing */);
    • Bad: Duplicate expensive operations

    View Slide

  69. Hot vs. Cold

    View Slide

  70. Sharing
    ConnectableObservable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));


    obs.connect();

    View Slide

  71. Sharing
    ConnectableObservable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));


    obs.connect();

    View Slide

  72. Sharing
    ConnectableObservable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));


    obs.connect();

    View Slide

  73. Sharing
    ConnectableObservable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));


    obs.connect();

    View Slide

  74. Sharing
    ConnectableObservable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));


    obs.connect();

    View Slide

  75. refCount
    Observable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish()
    .refCount();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));
    // Output
    1: 395093905388878
    2: 395093906717841

    View Slide

  76. refCount
    Observable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish()
    .refCount();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));
    // Output
    1: 395093905388878
    2: 395093906717841

    View Slide

  77. refCount
    Observable obs =

    Observable.fromCallable(() -> System.nanoTime())

    .publish()
    .refCount();


    obs.subscribe(l -> System.out.println("1: " + l));

    obs.subscribe(l -> System.out.println("2: " + l));
    // Output
    1: 395093905388878
    2: 395093906717841
    huh?

    View Slide

  78. Lessons
    • Share cold observables
    • Explicitly call connect()
    • Use autoConnect()

    View Slide

  79. it’s over!
    @danlew42

    View Slide