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

Where the Reactive Rubber Meets the Road

rjrjr
November 10, 2015

Where the Reactive Rubber Meets the Road

Square's simple idiom for binding RxJava to the UI without boilerplate or side effects.

https://www.youtube.com/watch?v=va1d4MqLUGY

rjrjr

November 10, 2015
Tweet

More Decks by rjrjr

Other Decks in Programming

Transcript

  1. Where the
    Reactive Rubber
    Meets the Road

    View Slide

  2. What could possibly go
    wrong?
    Hard Learned Lesson
    // We want to ensure that the animation end 

    // event happens after the transition has

    // actually finished.

    newChild.post(new Runnable() {

    @Override public void run() {

    animationEnded(newChild);

    }

    });

    View Slide

  3. What could possibly go
    wrong?
    Hard Learned Lesson
    // We want to ensure that the animation end 

    // event happens after the transition has

    // actually finished.

    newChild.installLandMine(new Runnable() {

    @Override public void run() {

    animationEnded(newChild);

    }

    });

    View Slide

  4. Every Post is a Race
    java.lang.NullPointerException:
    at com.squareup.ui.root.HomeDrawerPresenter.closeDrawer(HomeDrawerPresenter.java:303)
    at com.squareup.ui.root.HomeScreen$Presenter.onFlowAnimationEnd(HomeScreen.java:226)
    at com.squareup.ui.root.HomeView.onFlowAnimationEnd(HomeView.java:81)
    at com.squareup.flow.RegisterFlowContainerSupport.flowAnimationEnded(RegisterFlowContainerSupport.java:258)
    at com.squareup.flow.RegisterFlowContainerSupport.access$000(RegisterFlowContainerSupport.java:45)
    at com.squareup.flow.RegisterFlowContainerSupport$1.run(RegisterFlowContainerSupport.java:120)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5103)
    at java.lang.reflect.Method.invokeNative(Method.java:-2)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:606)
    at dalvik.system.NativeStart.main(NativeStart.java:-2)

    View Slide

  5. ‣ Millions of downloads
    ‣ Every code path, every corner case
    will be hit every day
    ‣ Each post() adds another
    combinatorial dimension
    Every Race
    Will be Run

    View Slide

  6. Keep your main thread synchronous

    View Slide

  7. Embrace the net’s asynchrony
    Keep your main thread synchronous

    View Slide

  8. Belly Burgers
    Unlike any Burger You’ve Ever Had.
    $1.99 fee • 40 - 60 min • $$
    Taqueria Cazado…
    Ole!
    $1.99 fee • 40 - 60 min • $

    View Slide

  9. Belly Burgers
    Unlike any Burger You’ve Ever Had.
    $1.99 fee • 40 - 60 min • $$
    Taqueria Cazado…
    Ole!
    $1.99 fee • 40 - 60 min • $
    Picasso

    View Slide

  10. Belly Burgers
    Unlike any Burger You’ve Ever Had.
    $1.99 fee • 40 - 60 min • $$
    Taqueria Cazado…
    Ole!
    $1.99 fee • 40 - 60 min • $
    Model
    Picasso

    View Slide

  11. RestaurantListView.java
    Ain’t Rx Grand?
    restaurants.restaurant(restaurantId)

    .observeOn(mainThread())
    .subscribe(new Action1() {

    @Override public void call(Restaurant
    restaurant) {

    nameView.setText(restaurant.name);
    // etc.

    }

    }));

    View Slide

  12. RestaurantListView.java
    Command-minus
    Ain’t Rx Grand?
    restaurants.restaurant(restaurantId)

    .observeOn(mainThread())
    .subscribe((Action1) (restaurant) -> {

    nameView.setText(restaurant.name);
    // etc.

    }));

    View Slide

  13. RestaurantListView.java
    Command-minus
    Ain’t Rx Grand?
    restaurants.restaurant(restaurantId)

    .observeOn(mainThread())
    .subscribe((Action1) (restaurant) -> {

    nameView.setText(restaurant.name);
    // etc.

    }));

    View Slide

  14. rx.android.schedulers.
    HandlerScheduler
    Uh Oh
    public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {

    if(this.compositeSubscription.isUnsubscribed()) {

    return Subscriptions.unsubscribed();

    } else {

    action = RxAndroidPlugins.getInstance().getSchedulersHook().onSchedule(action);

    final ScheduledAction scheduledAction = new ScheduledAction(action);

    scheduledAction.addParent(this.compositeSubscription);

    this.compositeSubscription.add(scheduledAction);

    this.handler.postDelayed(scheduledAction,
    unit.toMillis(delayTime));

    scheduledAction.add(Subscriptions.create(new Action0() {

    public void call() {

    HandlerWorker.this.handler.removeCallbacks(scheduledAction);

    }

    }));

    return scheduledAction;

    }

    }


    View Slide

  15. rx.android.schedulers.
    HandlerScheduler
    Uh Oh
    public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {

    if(this.compositeSubscription.isUnsubscribed()) {

    return Subscriptions.unsubscribed();

    } else {

    action = RxAndroidPlugins.getInstance().getSchedulersHook().onSchedule(action);

    final ScheduledAction scheduledAction = new ScheduledAction(action);

    scheduledAction.addParent(this.compositeSubscription);

    this.compositeSubscription.add(scheduledAction);

    this.handler.postDelayed(scheduledAction,
    unit.toMillis(delayTime));

    scheduledAction.add(Subscriptions.create(new Action0() {

    public void call() {

    HandlerWorker.this.handler.removeCallbacks(scheduledAction);

    }

    }));

    return scheduledAction;

    }

    }

    Just make it block if
    we’re already on the
    handler’s thread.

    View Slide

  16. rx.android.schedulers.
    HandlerScheduler
    Uh Oh
    public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {

    if(this.compositeSubscription.isUnsubscribed()) {

    return Subscriptions.unsubscribed();

    } else {

    action = RxAndroidPlugins.getInstance().getSchedulersHook().onSchedule(action);

    final ScheduledAction scheduledAction = new ScheduledAction(action);

    scheduledAction.addParent(this.compositeSubscription);

    this.compositeSubscription.add(scheduledAction);

    this.handler.postDelayed(scheduledAction,
    unit.toMillis(delayTime));

    scheduledAction.add(Subscriptions.create(new Action0() {

    public void call() {

    HandlerWorker.this.handler.removeCallbacks(scheduledAction);

    }

    }));

    return scheduledAction;

    }

    }

    If I could then
    I would…
    Just make it block if
    we’re already on the
    handler’s thread.

    View Slide

  17. Belly Burgers
    Unlike any Burger You’ve Ever Had.
    $1.99 fee • 40 - 60 min • $$
    Taqueria Cazado…
    Ole!
    $1.99 fee • 40 - 60 min • $
    Model

    View Slide

  18. Belly Burgers
    Unlike any Burger You’ve Ever Had.
    $1.99 fee • 40 - 60 min • $$
    Taqueria Cazado…
    Ole!
    $1.99 fee • 40 - 60 min • $
    Model

    View Slide

  19. Rx is single-threaded by default

    View Slide

  20. What starts on the main thread,
    stays on the main thread.
    restaurants.restaurant(restaurantId)

    .observeOn(mainThread())
    .subscribe((Action1) (restaurant) -> {

    nameView.setText(restaurant.name);
    // etc.

    }));

    View Slide

  21. RestaurantList.java
    Observables all the way down
    public Observable
    restaurant(final String id) {

    return restaurantsSubject

    .map((Func1) (restaurants) -> {

    for (Restaurant r : restaurants) {

    if (r.id.equals(id)) return r;

    }

    return null;

    }) 

    .filter(notNull());

    }

    View Slide

  22. RestaurantList.java
    Promise Keeper
    /** May be called from any thread. */
    public void update(
    List restaurants) {

    Observable.just(restaurants)

    .observeOn(mainThread())

    .subscribe((Action1) (list) {

    restaurantsSubject.onNext(list);

    });

    }


    View Slide

  23. RestaurantList.java
    Better: Promise Enforcer
    /** Must be called from the main thread. */
    public void update(
    List restaurants) {

    checkMainThread();

    restaurantsSubject.onNext(restaurants);

    }

    void checkMainThread() {

    if (myLooper() != getMainLooper()) {
    throw new AssertionError(
    “Must be called from main thread.”);
    }

    }


    View Slide

  24. Keep your main thread synchronous
    Embrace the net’s asynchrony
    Pick two

    View Slide

  25. Bonus Hack Round: Cleaner stack traces
    service.doThing(thing)

    .subscribe((Action1) (response) {

    onResponse(response);

    },

    (e) {

    if (e instanceof RetrofitError) {

    onNetworkError(e);

    } else {

    throw new
    OnErrorNotImplementedException(e);

    }

    });

    If you must handle errors

    View Slide

  26. @rjrjr
    https://squareup.com/careers

    View Slide