Keep Your Main Thread Synchronous

There was a really great talk last year by Ray Ryan (one of the developers at Square) titled Where the Reactive Rubber Meets the Road. It's only 7-minutes long, and if you use RxJava on Android it is definitely worth your time.

The key point is that the AndroidSchedulers.mainThread() scheduler internally uses a Handler and uses postDelayed to post actions onto the main thread. Ray points out that any post call can cause a race-condition and hence cause subtle, hard-to-reproduce NullPointerExceptions that you'll see occasionally when your app reaches a large enough scale.

For example, the following code can be considered potentially risky if it's in your UI:

someObservable  
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(something -> {
    // perform some action on some view that might not
    // exist anymore here. Potential NPE!
  });

In order to be safer, we should utilize the fact that RxJava is synchronous by default and "keep our main thread synchronous". If we subscribe to an Observable in the UI, it should ideally have no Schedulers applied to it.

Obviously this is an oversimplification though, as many Observable tasks (e.g. http requests) are inherently asynchronous. The solution that Ray suggests for keeping your main thread synchronous combined with asynchronous Observables involves using Subjects as a "middle man". Basically he creates a BehaviourSubject that gets sent items (onNext calls) from some long-running Observable (e.g. a network-request Observable). The long-running Observable has Schedulers applied to it, but the BehaviourSubject doesn't. Then, at any point, the UI can subscribe directly to the BehaviourSubject. If data has already been emitted by the long-running Observable, then the BehaviourSubject will emit it to the UI synchronously. If data hasn't arrived yet, the UI will receive the event whenever the Subject receives it (assuming it is still subscribed) without any additional post calls in the UI layer. Perfect!

Although the talk does an excellent job of explaining the problem and providing a solution to it, I'm still unclear on some implementation details. For example, it's not obvious how he handles errors in the UI, as his BehaviourSubject only emits items (not errors). For that reason, I wrote my own example code that solves the problem in a similar (but slightly modified) way.

My example can be found here. Internally, when value() is subscribed to, it creates and stores a ReplaySubject for the UI to subscribe to. Then, a composition of Observables is created to read from memory, disk, and network (as described in Dan Lew's blog). The memory-cached Observable is synchronous, so if we have a memory-cached value then it will immediately emit to the ReplaySubject. When the UI subscribes to that subject, it will receive the events synchronously too.

Working with your Observables in this layer, away from the UI, actually makes things a lot easier in terms of handling configuration changes too. If you run my sample app, you can rotate the screen while the fake network request is running, and the UI will simply unsubscribe and resubscribe to the same ReplaySubject with zero side effects.

If anyone else has thoughts/ideas on this, I'd love to hear from you on Twitter or Google+

Brad Campbell

Android application developer, currently working as the lead on the ANZ goMoney NZ applications. All of the opinions and code on this blog are my own, and not that of ANZ.