An alternative to RxAndroidPlugins and RxJavaPlugins: Scheduler Injection

Peter Tackage
4 min readDec 13, 2016

In my previous article, I described how the RxAndroidPlugins APIs for RxJava 2 can be used to override Schedulers to create a suitable execution environment for JVM-based unit tests.

As I hinted, that’s not your only option. In this follow-up, I will describe another possibility; Scheduler Injection and we’ll see how it compares to using the plugins approach.

If you are familiar with RxJava 1.x, but haven’t yet started with RxJava 2.x; don’t worry, I’ll mention some specifics from 2.x, but the principles described are still generally applicable to both versions.

Scheduler Injection?

Scheduler Injection is simply supplying the desired Scheduler instance to your operators, rather than letting them run on their defaults.

It is most succinctly performed by employing the Strategy Pattern. You start by declaring an interface; SchedulerProvider which defines access to all the Schedulers that your app uses.

public interface SchedulerProvider {
Scheduler ui();
Scheduler computation();
Scheduler io();
Scheduler special();
// Other schedulers as required…
}

In your production code, you then implement this interface so that it returns the corresponding Scheduler instances from RxAndroid, RxJava or your own custom Scheduler. It would look something like this:

final class AppSchedulerProvider implements SchedulerProvider {
@Override
public Scheduler ui() {
return AndroidSchedulers.mainThread();
}
@Override
public Scheduler computation() {
return Schedulers.computation();
}
@Override
public Scheduler io() {
return Schedulers.io();
}
@Override
public Scheduler special() {
return MyOwnSchedulers.special();
}
}

Now wherever a Scheduler is used, you supply that class with the SchedulerProvider instance (via constructor or dependency injection framework) and reference the corresponding Scheduler in your operators. For example, say we originally have the sequence:

 bookstoreModel.getFavoriteBook()
.map(Book::getTitle)
.delay(5, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(view::setBookTitle));

This becomes -

bookstoreModel.getFavoriteBook()
.map(Book::getTitle)
.delay(5, TimeUnit.SECONDS,
this.schedulerProvider.computation())

.observeOn(this.schedulerProvider.ui())
.subscribe(view::setBookTitle));

Importantly, all explicit and implicit use of Schedulers must be replaced by their equivalent from the SchedulerProvider. Naturally this includes those referenced in operators such as observeOn and subscribeOn, but also in operators like delay and timeout which operate on a particular Scheduler unless otherwise specified.

@SchedulerSupport

In RxJava 2, you can tell if an operator implicitly uses a particular Scheduler by inspecting the @SchedulerSupport annotation on its declaration. This annotation has no inherent run-time effect, but rather it serves as documentation of the Scheduler used by the operator.

If we look at the delay operator in RxJava 2’s Observable class we can see that its @SchedulerSupport annotation has the value SchedulerSupport.COMPUTATION, meaning that it uses Schedulers.computation() internally -

RxJava 2.x delay operator using Schedulers.computation()

These types of operators will also have an overloaded equivalent, annotated with the value SchedulerSupport.CUSTOM, which allow you to manually specify the Scheduler to be used -

RxJava 2.x delay operator with Scheduler parameter

You just need to change your operator method invocation to the overloaded variant and pass in the appropriate Scheduler. Pretty simple!

Operators that don’t use a Scheduler, such as map, have the annotation value SchedulerSupport.NONE. Their invocations won’t require any changes.

In Your Tests

Now as we know from my previous article, we can’t use the AppSchedulerProvider implementation in our JVM-based unit tests due to the Android dependencies and asynchronous scheduling; we need a different implementation.

The exact implementation depends on the particular requirements of your test. That’s where the Scheduler Injection pattern shines; you can override the Schedulers in whatever way you like and modify them whenever it suits you.

In our example, we want to verify the influence of the delay operator, so we would use a SchedulerProvider backed by an instance of RxJava’s TestScheduler.

This is quite a straightforward task; it’s just another implementation of SchedulerProvider injected into the class under test. See the example project for the details.

Verbose but Flexible

When compared to using the Plugins, you can see that the additional constructor dependency and operator parameter could add a bit of unwanted verbosity and wiring ceremony to your code.

Another potential concern is that you might occasionally forget to supply the additional parameter to the operator, leading to unpredictable test results and wasted time when debugging.

On the upside, using the SchedulerProvider gives you complete flexibility and control over your Schedulers. You won’t need to be concerned with the quirks of the Plugin APIs, nor will you need to get your head around the Plugin API changes from RxJava 1.x. Furthermore, if you are using your own custom Scheduler, then it’s simple to add that into your SchedulerProvider and override it when necessary.

All this means that with the SchedulerProvider, you have a single place where you can freely manage your Schedulers.

Decision Time

So what approach should be used? Should you use the Plugins or use Scheduler Injection?

My recommendation for app developers is to use Scheduler Injection.

I’ve used it in large production apps and in my experience the flexibility that you gain outweighs the issue of verbosity. With a little bit of repetition and awareness, using it becomes second nature. I haven’t found myself forgetting to supply a Scheduler; any reasonably comprehensive set of tests would make that painfully obvious to you.

The Plugins might be less intrusive to your production code, but when it comes time to test, their limited flexibility and somewhat quirky initialization sequence eliminates those gains.

So when would you want to use RxJavaPlugins or RxAndroidPlugins?

Well, they remain helpful for library developers who might not want to expose Scheduler configuration to clients, but want control when testing. Also, they could be an option if you’ve inherited a legacy code-base which somehow makes extensive use of RxJava, but not of dependency injection. Hopefully that’s not your situation!

So, if you can, go down the Scheduler Injection path and write your own SchedulerProvider.

Thanks for reading!

See the full example source code here.

--

--