What could possibly go wrong? Hard Learned Lesson // We want to ensure that the animation end // event happens after the transition has // actually finished.
What could possibly go wrong? Hard Learned Lesson // We want to ensure that the animation end // event happens after the transition has // actually finished.
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)
‣ 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
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.
What starts on the main thread, stays on the main thread. restaurants.restaurant(restaurantId) .observeOn(mainThread()) .subscribe((Action1) (restaurant) -> { nameView.setText(restaurant.name); // etc. }));
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()); }
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); }); }
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.”); } }