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

Where the Reactive Rubber Meets the Road

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
Avatar for rjrjr 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

Avatar for rjrjr

rjrjr

November 10, 2015

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