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. 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);
 }
 });
  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.installLandMine(new Runnable() {
 @Override public void run() {
 animationEnded(newChild);
 }
 });
  3. 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)
  4. ‣ 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
  5. Belly Burgers Unlike any Burger You’ve Ever Had. $1.99 fee

    • 40 - 60 min • $$ Taqueria Cazado… Ole! $1.99 fee • 40 - 60 min • $
  6. 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
  7. 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
  8. 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;
 }
 }

  9. 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.
  10. 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.
  11. 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
  12. 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
  13. What starts on the main thread, stays on the main

    thread. restaurants.restaurant(restaurantId)
 .observeOn(mainThread()) .subscribe((Action1) (restaurant) -> {
 nameView.setText(restaurant.name); // etc.
 }));
  14. RestaurantList.java Observables all the way down public Observable<Restaurant> restaurant(final String

    id) {
 return restaurantsSubject
 .map((Func1) (restaurants) -> {
 for (Restaurant r : restaurants) {
 if (r.id.equals(id)) return r;
 }
 return null;
 }) 
 .filter(notNull());
 }
  15. RestaurantList.java Promise Keeper /** May be called from any thread.

    */ public void update( List<Restaurants> restaurants) {
 Observable.just(restaurants)
 .observeOn(mainThread())
 .subscribe((Action1) (list) {
 restaurantsSubject.onNext(list);
 });
 }

  16. RestaurantList.java Better: Promise Enforcer /** Must be called from the

    main thread. */ public void update( List<Restaurants> restaurants) {
 checkMainThread();
 restaurantsSubject.onNext(restaurants);
 }
 void checkMainThread() {
 if (myLooper() != getMainLooper()) { throw new AssertionError( “Must be called from main thread.”); }
 }

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