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

Parth Padgaonkar - RxJava Extended

Parth Padgaonkar - RxJava Extended

droidcon Berlin

July 17, 2018
Tweet

More Decks by droidcon Berlin

Other Decks in Programming

Transcript

  1. new ClickObservable(footerButton) .observeOn(AndroidSchedulers.mainThread()) .flatMap(!__ !-> getNetworkPassword(adapter.list.get(adapter.selected))) .flatMap(networkInfoPair !-> { return

    interceptWifiWatchdogs() .andThen(Completable.defer(this!::handlePreviousSetupAttempt)) .observeOn(AndroidSchedulers.mainThread()) .andThen(Single.defer(this!::handleMarshmallowPermissions)) .map(!__ !-> networkInfoPair); }) .flatMap(pair !-> provisionWifiDevice(pair.first, pair.second)) .flatMap(!__ !-> awaitDeviceOnline(lock, targetDevice) .flatMap(isOnline !-> { if (isOnline) return Single.just(isOnline); else return onWifiConfigError(new VenusSetupError(VenusError.BRIDGE_OFFLINE)) })) .observeOn(AndroidSchedulers.mainThread()) .onErrorResumeNext(this!::onWifiConfigError) .subscribe(o !-> completionSignal.onComplete(), 
 throwable !-> completionSignal!::onError); Extending RxJava Writing Your First Custom Operator 1 bit.ly/2lBsBge
  2. 2

  3. 3

  4. Kotlin? 4 +public class ObservableFilterMap<In, Out> extends Observable<Out> { private

    final Function<In, Out> mapper; private final Predicate<In> filter; public ObservableFilterMap(Predicate<In> filter, Function<In, Out> mapper) { this.filter = filter; this.mapper = mapper; } @Override protected void subscribeActual(Observer<? super Out> observer) { new MapFilterObserver!<>(observer, filter, mapper); } 
 !!... } ==
  5. Table of Contents I. What is RxJava? II. Anatomy of

    a Custom Operator ‣ ClickObservable ‣ MapFilterObservable III. Observable!::create IV.Questions 5
  6. Backpressure. • rapid/large number of emissions • bursty • multiple

    mismatched sources 9 • slow to consume 
 (consumption is expensive) • blocking I/O • network • disk • carrier pigeon Producer (upstream) Consumer (downstream)
  7. Anatomy of a Custom Operator 1. construct 2. subscribeActual(Observer<?> observer)

    3. inner listener... a. implements non-Reactive class ("bridge") b. implements Observer<?> 14
  8. Anatomy of a Custom Operator 1. construct 2. subscribeActual(Observer<?> observer)

    3. inner listener... a. implements non-Reactive class ("bridge") b. implements Observer<?> 15
  9. Custom Operator? 16 new ClickObservable(footerButton) .observeOn(AndroidSchedulers.mainThread()) .flatMap(!__ !-> getNetworkPassword(adapter.list.get(adapter.selected))) .flatMap(networkInfoPair

    !-> { return interceptWifiWatchdogs() .andThen(Completable.defer(this!::handlePreviousSetupAttempt)) .observeOn(AndroidSchedulers.mainThread()) .andThen(Single.defer(this!::handleMarshmallowPermissions)) .map(!__ !-> networkInfoPair); }) .flatMap(pair !-> provisionWifiDevice(pair.first, pair.second)) .flatMap(!__ !-> awaitDeviceOnline(lock, targetDevice) .flatMap(isOnline !-> { if (isOnline) return Single.just(isOnline); else return onWifiConfigError(new VenusSetupError(VenusError.BRIDGE_OFFLINE)) })) .observeOn(AndroidSchedulers.mainThread()) .onErrorResumeNext(this!::onWifiConfigError) .subscribe(o !-> completionSignal.onComplete(), 
 throwable !-> completionSignal!::onError);
  10. Custom Operator! 17 new ClickObservable(footerButton) .observeOn(AndroidSchedulers.mainThread()) .flatMap(!__ !-> getNetworkPassword(adapter.list.get(adapter.selected))) .flatMap(networkInfoPair

    !-> { return interceptWifiWatchdogs() .andThen(Completable.defer(this!::handlePreviousSetupAttempt)) .observeOn(AndroidSchedulers.mainThread()) .andThen(Single.defer(this!::handleMarshmallowPermissions)) .map(!__ !-> networkInfoPair); }) .flatMap(pair !-> provisionWifiDevice(pair.first, pair.second)) .flatMap(!__ !-> awaitDeviceOnline(lock, targetDevice) .flatMap(isOnline !-> { if (isOnline) return Single.just(isOnline); else return onWifiConfigError(new VenusSetupError(VenusError.BRIDGE_OFFLINE)) })) .observeOn(AndroidSchedulers.mainThread()) .onErrorResumeNext(this!::onWifiConfigError) .subscribe(o !-> completionSignal.onComplete(), 
 throwable !-> completionSignal!::onError);
  11. Custom Operator - Constructor 18 final class ClickObservable extends Observable<Boolean>

    { ClickObservable(View view) { !!... } @Override protected void subscribeActual(Observer<? super Boolean> observer) { !!... }
 } Source(ish): JakeWharton/RxBinding
  12. Custom Operator - Constructor 19 final class ClickObservable extends Observable<Boolean>

    { private final View view; ClickObservable(View view) { this.view = view; } @Override protected void subscribeActual(Observer<? super Boolean> observer) { !!... }
 !!... Source(ish): JakeWharton/RxBinding
  13. Custom Operator: Upstream Subscription 20 final class ClickObservable extends Observable<Boolean>

    { private final View view; ClickObservable(View view) { this.view = view; } @Override protected void subscribeActual(Observer<? super Boolean> observer) { Listener listener = new Listener(view, observer); observer.onSubscribe(listener); view.setOnClickListener(listener); }
 !!... Source(ish): JakeWharton/RxBinding
  14. Custom Operator: Upstream Subscription 21 final class ClickObservable extends Observable<Boolean>

    { private final View view; ClickObservable(View view) { this.view = view; } @Override protected void subscribeActual(Observer<? super Boolean> observer) { Listener listener = new Listener(view, observer); observer.onSubscribe(listener); view.setOnClickListener(listener); }
 !!... Source(ish): JakeWharton/RxBinding void onSubscribe(@NonNull Disposable d);
  15. Custom Operator: Inner "Bridge" 22 static final class Listener extends

    MainThreadDisposable implements OnClickListener { public Listener(View view, Observer<? super Boolean> observer) { !!... } @Override public void onClick(View v) { !!... } @Override protected void onDispose() { !!... }
 } Source(ish): JakeWharton/RxBinding
  16. Custom Operator: Inner "Bridge" 23 static final class Listener extends

    MainThreadDisposable implements OnClickListener { private final View view; private final Observer<? super Boolean> observer; public Listener(View view, Observer<? super Boolean> observer) { this.view = view; this.observer = observer; } @Override public void onClick(View v) { if (!isDisposed()) { observer.onNext(Boolean.TRUE); } } } Source(ish): JakeWharton/RxBinding
  17. Custom Operator: Inner "Bridge" 24 static final class Listener extends

    MainThreadDisposable implements OnClickListener { private final View view; private final Observer<? super Boolean> observer; public Listener(View view, Observer<? super Boolean> observer) { this.view = view; this.observer = observer; } @Override protected void onDispose() { view.setOnClickListener(null); } @Override public void onClick(View v) { if (!isDisposed()) { observer.onNext(Boolean.TRUE); } } } Source(ish): JakeWharton/RxBinding
  18. SeekBarObservable!::Listener 25 private static class Listener extends MainThreadDisposable implements OnSeekBarChangeListener

    { private final SeekBar seekBar; private final Observer<? super SeekBarRxEvent> observer; public Listener(SeekBar seekBar, Observer<? super SeekBarRxEvent> observer) { this.seekBar = seekBar; this.observer = observer; } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (!fromUser) return; observer.onNext(new SeekBarRxEvent(EventType.PROGRESS_CHANGED, progress)); } @Override public void onStopTrackingTouch(SeekBar seekBar) { observer.onNext(new SeekBarRxEvent(EventType.STOP_TRACKING, seekBar.getProgress())); } @Override public void onStartTrackingTouch(SeekBar seekBar) { !/*no op!*/ } @Override protected void onDispose() { seekBar.setOnSeekBarChangeListener(null); } } Source(ish): JakeWharton/RxBinding
  19. Anatomy of a Custom Operator 1. construct 2. subscribeActual(Observer<?> observer)

    3. inner listener... a. implements non-Reactive class b. implements Observer<?> 26
  20. Map+Filter Hybrid: main class 27 public class ObservableFilterMap<In, Out> extends

    Observable<Out> { private final Function<In, Out> mapper; private final Predicate<In> filter; public ObservableFilterMap(Predicate<In> filter, Function<In, Out> mapper) { this.filter = filter; this.mapper = mapper; } @Override protected void subscribeActual(Observer<? super Out> observer) { new MapFilterObserver!<>(observer, filter, mapper); } 
 !!... }
  21. Map+Filter Hybrid: Inner Skeleton 28 static final class MapFilterObserver<In, Out>

    implements Observer<In>, Disposable{ MapFilterObserver(Observer<Out> child, Predicate<In> filter, Function<In, Out> mapper) { !!... } @Override public void onSubscribe(@NonNull Disposable d) { !!... } @Override public void dispose() { !!... } @Override public boolean isDisposed() { !!... } @Override public void onNext(@NonNull In in) { !!... } @Override public void onError(@NonNull Throwable e) { !!... } @Override public void onComplete() { !!... } }
  22. Map+Filter Hybrid: Handling Subscription 29 static final class MapFilterObserver<In, Out>

    implements Observer<In>, Disposable{ !!... private Disposable d; MapFilterObserver(Observer<Out> downstream, Predicate<In> filter, Function<In, Out> mapper) {!!...} @Override public void onSubscribe(@NonNull Disposable d) { if (DisposableHelper.validate(this.d, d)) { this.d = d; observer.onSubscribe(this); } } @Override public void dispose() { d.dispose(); } @Override public boolean isDisposed() { return d.isDisposed(); }
  23. Map+Filter Hybrid: Handling Disposal 30 static final class MapFilterObserver<In, Out>

    implements Observer<In>, Disposable{ !!... private Disposable d; MapFilterObserver(Observer<Out> downstream, Predicate<In> filter, Function<In, Out> mapper) {!!...} @Override public void dispose() { d.dispose(); } @Override public boolean isDisposed() { return d.isDisposed(); } @Override public void onSubscribe(@NonNull Disposable d) { if (DisposableHelper.validate(this.d, d)) { this.d = d; observer.onSubscribe(this); } }
  24. Map+Filter Hybrid: Terminal Conditions 31 static final class MapFilterObserver<In, Out>

    implements Observer<In>, Disposable{ !!... @Override public void onError(@NonNull Throwable e) { downstream.onError(e); } @Override public void onComplete() { downstream.onComplete(); } protected final void fail(Throwable t) { Exceptions.throwIfFatal(t); s.dispose(); onError(t); } }
  25. Map+Filter Hybrid: OnNext 32 static final class MapFilterObserver<In, Out> implements

    Observer<In>, Disposable{ !!... @Override public void onNext(@NonNull In t) { boolean test; try { test = filter.test(t); } catch (Throwable e) { fail(e); return; } if (test) { Out out; try { out = ObjectHelper.requireNonNull(mapper.apply(t), "Mapper returned null!"); } catch (Throwable ex) { fail(ex); return; } downstream.onNext(out); } } }
  26. Putting it all together... 33 public Disposable trivium() { return

    new ClickObservable(view) .map(b !-> "the quick brown fox jumped over the lazy dogs") .flatMapIterable(sentence !-> Arrays.asList(sentence.split("\)s"))) .flatMap(letter !-> new ObservableFilterMap<String, String>( l !-> l.length() > 0, String!::toUpperCase)) .distinct() .sorted() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .zipWith(Observable.range(1, 100), (letter, count) !-> String.format("%2d. %s", count, letter)) .subscribe(System.out!::println, Throwable!::printStackTrace); }
  27. Putting it all together... 34 public Disposable trivium() { return

    new ClickObservable(view) .map(b !-> "the quick brown fox jumped over the lazy dogs") .flatMapIterable(sentence !-> Arrays.asList(sentence.split("\)s"))) .flatMap(letter !-> new ObservableFilterMap<String, String>( l !-> l.length() > 0, String!::toUpperCase)) .distinct() .sorted() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .zipWith(Observable.range(1, 100), (letter, count) !-> String.format("%2d. %s", count, letter)) .subscribe(System.out!::println, Throwable!::printStackTrace); }
  28. Putting it all together... 35 public Disposable trivium() { return

    new ClickObservable(view) .map(b !-> "the quick brown fox jumped over the lazy dogs") .flatMapIterable(sentence !-> Arrays.asList(sentence.split("\)s"))) .flatMap(letter !-> new ObservableFilterMap<String, String>( l !-> l.length() > 0, String!::toUpperCase)) .distinct() .sorted() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .zipWith(Observable.range(1, 100), (letter, count) !-> String.format("%2d. %s", count, letter)) .subscribe(System.out!::println, Throwable!::printStackTrace); }
  29. Find the bug 37 Observable<Boolean> clickRx(@IdRes int id) { View

    button = findViewById(id); return Observable.create(emitter !-> { button.setOnClickListener(v !-> emitter.onNext(Boolean.TRUE)); }); }
  30. Find the bug 38 Observable<Boolean> clickRx(@IdRes int id) { View

    button = findViewById(id); return Observable.create(emitter !-> { button.setOnClickListener(v !-> emitter.onNext(Boolean.TRUE)); emitter.setDisposable(Disposables.fromAction(() !-> button.setOnClickListener(null))); }); }
  31. Find the bug 39 Observable<Boolean> clickRx(@IdRes int id) { View

    button = findViewById(id); return Observable.create(emitter !-> { button.setOnClickListener(v !-> emitter.onNext(Boolean.TRUE)); emitter.setDisposable(Disposables.fromAction(() !-> button.setOnClickListener(null))); }); }
  32. Remember Me? 40 final class ClickObservable extends Observable<Boolean> { private

    final View view; ClickObservable(View view) { this.view = view; } @Override protected void subscribeActual(Observer<? super Boolean> observer) { Listener listener = new Listener(view, observer); observer.onSubscribe(listener); view.setOnClickListener(listener); }
 !!... Source(ish): JakeWharton/RxBinding void onSubscribe(@NonNull Disposable d);
  33. Decisions, decisions... 41 Separate Class w/ inner class Observable!::create handling

    Disposable is mandatory minimum 30 lines of (Java) code Literally 3 lines of code Memory leaks...maybe.
  34. Great power, great responsibility When you have a hammer, everything

    looks like a nail 42 • Rx is a tool • custom operators: one tool in your toolkit • really bad: business logic • "meh": combining existing operators
 (Transformers: "Don't Break the Chain" - Dan Lew) • good: bridge your non-Rx streams into Rx
  35. Things I haven't covered...yet! • Operator fusion (inlining) • Serializing

    onNext + terminal events (concurrency) • Backpressure strategies (source + intermediate operators) • request accounting • queue-draining strategies 43
  36. Links/Sources/Inspiration/Future Reading 44 • Backpressure (Karnok) bit.ly/2GymQJl • JakeWharton/RxBinding •

    ViewClickObservable • SeekBarChangeObservable bit.ly/2GvXLmi bit.ly/2ICy4wQ bit.ly/2qjiRKz • Writing Operators (Karnok) bit.ly/2H7wS5b • Operator Fusion (1 of 4) (Karnok) bit.ly/2GwMBxF • Map+Filter hybrid (Gist) bit.ly/2q2XTOR • Presentation.this bit.ly/2lBsBge