Java libraries with Kotlin. The case of dependency injection frameworks

3,472 reads

Kotlin opened up a new world for us. Developers write less code than before in Java world. And Kotlin’s interoperability with Java made it easy to incorporate it into existing and new apps, while still having the freedom of using our favourite Java libraries.

However, as these libraries are written in Java, consuming them in Kotlin often results in Java-like syntax. This does not mean we need to reinvent the wheel and write a Kotlin version of that library (something I see a lot nowadays). But maybe we can write some Kotlin layers around it.

A good example is Kotlin Mockito by Niek Haarman which took away the ugliness in using Mockito in Kotlin — something that was badly needed

Often it’s not that bad as with Mockito, and so we keep going.

But we should not! We should take a deep breath and see if we can improve it.

When it comes to dependency injection, I am not the biggest fan of Dagger due to the ridiculous amount of boilerplate code required to set it up. In addition it’s not really good for testing. And for me testing is one of the reasons for dependency injection in the first place. So I like the approach of Toothpick. Started as a project at Groupon (an app with tens of millions of downloads) by open source hero Stephane Nicolas and his colleague Daniel Molinero Reguera it was meant as a fresh approach to Dependency Injection some time ago, avoiding much of the boilerplate of Dagger 1+2. One of it’s strength is testing, it was built with testing in mind.

But using it in Kotlin project lead me to the point where I needed to improve it as my eyes where hurting.

Let’s look how we bind some classes to a module:

val module = Module()

module.bind(Repository::class.java).to(DbRepository::class.java) module.bind(Scheduler::class.java).toInstance(DefaultScheduler())

module.bind(Api::class.java).toProviderInstance(ApiProvider())

Toothpick can bind a class to either another class or an exiting instance or a provider class that can create the class needed at runtime.

Let’s first make this a bit nicer with Kotlin:

val module = Module().apply {

bind(Repository::class.java).to(DataBaseRepository::class.java)

bind(Scheduler::class.java).toInstance(DefaultScheduler())

bind(Api::class.java).toProviderInstance(ApiProvider())

}

Some things that should bother us:

to is an existing extension function to create instance of Pair . As of the same name it’s really easy to mess this up here without even noticing. Your binding might not bind a thing but just create an unused Pair instance

So we need to write it like this to make sure we get the right to function:

bind(Repository::class.java).`to`(DataBaseRepository::class.java)

And soon we are in the centre of ugly code ;)

So let us improve step by step.

First: it would be nicer to not pass the type but use generics:

bind<Repository>().`to`(DataBaseRepository::class.java)

to call the original bind() method we need the java Class instance. In Kotlin it is possible to get this from a generic type if we mark the type reified (works only for functions declares as inline, which makes sense)

inline fun <reified T> Module.bind()

: Binding<T> = bind(T::class.java)

This way we added a new bind to the Module as extension function.

But now we have an empty method call. We should use that instead of just having it to call next method. So let’s build something so we can call it like this:

bindClass<Repository>(DataBaseRepository::class.java)

It’s not that hard either:

import kotlin.to as toPair

inline fun <reified T> Module.bindClass(target: Class<out T>)

: Binding<T> = bind<T>().apply { to(target) }

So we are calling the other bind method we just created and use apply to add a call to to() on it.

The import changes the name of the kotlin to function so no conflict exists here.

Now we still pass the Java class version. Do we need to? I don’t think so. So it should be:

bindClass<Repository>(DataBaseRepository::class)

The implementation is very similar. We just need to take a KClass:

inline fun <reified T> Module.bindClass(target: KClass<out Any>)

: Binding<T> = bind<T>().apply { to(target.java as Class<T>) }

So this works nice when passing class names.

When looking at the other variants, wouldn’t it be nice to pass a lambda?

bindInstance<Scheduler> { DefaultScheduler() }

It is very easy. This syntax can be uses if the last parameter of the function is a lambda:

inline fun <reified T> Module.bindInstance(target: () -> T)

: Binding<T> = bind<T>().apply { toInstance(target()) }

This brings me to the next idea.

When we use a provider we are writing lots of boilerplate:

class ApiProvider : Provider<Api> {

override fun get(): Api {

return RestApi()

}

}

bind(Api::class.java).toProviderInstance(ApiProvider())

Can we pass a lambda here too?

Probably! But let’s go step by step:

Applying what we did above would lead us to:

bindProviderInstance<Api>(ApiProvider())

Actually the generic can be omitted here as Provider already defined the generic, the amazing feature of type inference at work.

But instead of writing the provider, what we want is to write a lambda like this:

bindProviderInstance<Api>{ RestApi() }

The code behind it would be:

inline fun <reified T> Module.bindProviderInstance(target: () -> T)

: Binding<T> = bind<T>().apply {

toProviderInstance(target.asProvider()) }

where asProvider() is another extension method that hides creating the provider boilerplate:

fun <T> (() -> T).asProvider(): Provider<T> {

return object : Provider<T> {

override fun get(): T {

return invoke()

}

}

}

Let’s look at all the variations first in Java style:

class ApiProvider : Provider<Api> {

override fun get(): Api {

return RestApi()

}

}

val module = Module()

module.bind(Repository::class.java).to(DbRepository::class.java) module.bind(Scheduler::class.java).toInstance(DefaultScheduler())

module.bind(Api::class.java).toProviderInstance(ApiProvider())

and now in our new style

val module = Module().apply {

bind<Repository>(DataBaseRepository::class)

bindInstance<Scheduler>(DefaultScheduler())

bindProviderInstance<Api>{ RestApi() }

}

But although apply() is nice way here, it often gives the hint that we are working on a Java class too. So let’s improve this:

val module = module {

bind<Repository>(DataBaseRepository::class)

bindInstance<Scheduler>(DefaultScheduler())

bindProviderInstance<Api>{ RestApi() }

}

This definition is easy too. A function module that takes a lambda that runs in context of Module and returns a module:

fun module(bindings: Module.() -> Binding<*>)

: Module = Module().apply { bindings() }

And if you just have one module, let’s put this right in the scope:

val scope = simpleScope(SCOPE) {

bind<Repository>(DataBaseRepository::class)

bindInstance<Scheduler>(DefaultScheduler())

bindProviderInstance<Api>{ RestApi() }

}

where:

fun simpleScope(scopeName: Any, bindings: Module.() -> Binding<*>?)

: Scope = openScope(scopeName).apply {

Module().apply { bindings() }}

So it is very easy to improve an API that was designed for Java for Kotlin

And as sharing is caring. Feel free to import all of this directly from here.

Like I said in my previous post: if something is bothering you, improve it! #lifelong #learner

Tags