Friday, March 9, 2018

DI on Android (Kotlin) using an SPI

Most developers are intimately familiar with what an API is. However, mention SPI, and many will look like a question mark. This blog entry explains what it is, discusses its merits/shortcomings and shows how it can serve as a simple Inversion of Control or Dependency Injection pattern on Android for providing testing mocks.

So what is an SPI anyway?

The term SPI is an abbreviation of Service Provider Interface, and it's nothing more than a contract (interface) with pluggable implementations (classes) discovered lazily at runtime. I have also seen this pattern referred to as Service Locator and Service Registry. It likely has a older history than I am aware of (you can use it if you have introspection and reflection available in your language) but I made its acquaintance with Java 1.3 which introduced these service providers as a way of facilitating loosely coupled modules. In Java 6 the pattern was embraced further by the addition of the SericeLoader class and whenever you use an XML parser, a JSON parserspecial character sets, an encryption cipher, an image decoder etc. on the JVM you likely went through an SPI to get to it.

How does it work?

I was going to explain how it works in detail but it occurred to me, that the online Android documentation for ServiceLoader does it better than I possibly could! So let me just sum it up; all you do is:
  1. Declare that you implement a given interface by providing a /META-INF/services/INTERFACE_NAME file with the content pointing to the class implementing the interface.
  2. In your app at runtime, you use ServiceLoader to obtain an instance to the implementation (note that there could be more than one).
  3. Use Gradle to configure the implementations

I actually think of this as Dependency Injection and it was a thing long before people started writing complex libraries for achieving IoC/DI (Spring, Seam, Dagger, Guice, EJB3, Koin etc.). The beauty of this pattern is that you do not need a dependency injection container, you do not need a mocking library and there is no configuration beyond what you include in your classpath. It obviously has limitations over the full-fledged DI containers and it also suffers from the problem of being dynamic - as in, there is no compile-time verification when you assemble your application and there is a theoretical runtime overhead of doing discovery and class-loading. As you have probably guessed, I am a fan of Inversion of Control but not necessarily a fan of the many Dependency Injection manifestations that pop up; they all start out wanting to solve a simple problem but usually turn out to become just another layer of indirection one has to comprehend.

Android and Kotlin specifics

I link to the Android documentation for ServiceLoader above, so that obviously means the patterns also works on the Android platform. For it to work for an Android ARchive module (AAR), you need to create a /resources folder (New -> Folder -> Java Resources Folder inside Android Studio) and in here place your service registration file /META-INF/services/INTERFACE_NAME.

You can also use plain vanilla Java JAR's to the same effect. Since Java and Kotlin interoperates so wonderfully, things works equally well using Kotlin!

Dependency Injection through the classpath

I use this mechanism in order to be able to swap out a real (remote) web-service repository with a fake/mock (local) one, so that my unit-tests can run super fast. Depending on your needs, you may also have your functional/UI tests use this fake/mock - or you could even provide a 3'rd implementation - all through the power of Gradle.

If you are a commuter like I am, with flaky Internet connection on the train, this also gives you a way of remaining productive during your transit to/from work. You may do this simply by selecting a Gradle buildtype.

Of course, Android also allows you to use flavors if you makes more sense to your project requirements.

As I mention further up and as should be immediate apparent from reading the documentation for ServiceLoader, you could have multiple implementation of a given interface. Sometimes you really just need one so you grab the first you can get while other times you go through all. At some point, you are likely to require a chain, order or priority, in which case you may simply add a method to your interface and let the various implementations specify their relative order - akin to how you work with z-order for a UI.

It's not rocket science and it has its limitations, but if all you are after is loose coupling and a way to work with multiple implementation of something (in my world this oftes comes up in testing), then the build-in SPI approach is a really simple and powerful mechanism. I am surprised more people don't know about it but resort to much more complex solutions when its really overkill - let me know in the comments what you think. 

Post a Comment