
Dependency Injection with Kotlin — #Kodein #KOIN
Dependency injection is an instrumental technique, used to decouple dependencies from your code. Today, I want to look into how we can implement it in Kotlin. I will focus mostly on usage in the Android environment, though both libraries I will mention, are not limited to this environment.
Before we dive into the code, I want to stop a little bit in which state we are now. If you have an app older than a year before Google announced the support for Kotlin, there is high probability it’s still in large part written in Java. And uses Dagger 2 for dependency injection (maybe you are not using DI at all, then god with you).
Before Kotlin came, Dagger was for quite some time the de-facto standard for DI in the Android world. Developed by Google, it uses annotations and compile-time code generation to create your dependency graph and provide dependencies. No blame here, I was also using it for quite some time, but as it happens, it’s a framework developed in Java world. With Kotlin in the light now, we have new features and time came for the Dagger replacement, which will utilize them.
Welcome KODEIN (KOtlin DEpendency INjection)
To borrow just a few lines from its website, it brings you the following:
- small library, uses
inline
functions - DSL for dependency definition (no annotations, no code generation)
- no type erasure
Sounds interesting to you? Let’s add it to our project!
Open your build.gradle
file and add the following:
KODEIN extensively utilizes inline
functions (so its nice not to get over your DEX limit).
Make our application KodeinAware
To make our dependencies accessible using application Context
, we need to make our custom Application
class implement KodeinAware
interface.
Now we are ready to declare our bindings. So for that, let’s imagine a simple app. Don’t expect a lot of creativity; it will be a simple TODO list app. A single screen where you can add new tasks by pressing a button.
For this app, we will have a TasksRepository
, which holds the tasks and allows us to store new.
Bindings and modularity
As you can see above, we are providing a binding of TasksRepository
called FakeTasksRepository
.
The way this works is that in our Kodein
block, we start a binding with the keyword bind<T>()
where T
will be our bound type.
bind<TasksRepository>()
Then we continue and write with
,like if we were writing a sentence.
bind<TasksRepository>() with
and finally, a binding function. There are various types, for simplicity, we use singleton {}
here. It lazyly calls the function block the first time you access our TasksRepository
and then keeps the instance for the lifetime of your application (I will mention more binding functions below).
But we can do better, imagine, in more complicated app having all bindings in one file! As with Dagger, with KODEIN we can also split our app in modules. So let’s rewrite our binding into a Kodein.Module
.
To include the binding in our application Kodein
, we just have to import it
Retrieving dependencies
As we have successfully defined our TasksRepository
, we can now retrieve it!
In the example above, we assume that we can directly access our kodein
instance.
As you can see, thanks to type inference, we just call instance()
function and receive the appropriate instance (or Kodein.NotFoundException
if no binding is found).
Because we are on Android here, let’s see how we would do it in an Activity.
We have our Activity
class, retrieving the TasksRepository
instance. Thanks to some amazing Kotlin features like property delegation the retrieval is as easy (or even easier) as definition.
To access our Kodein instance, we implement the interface AppCompatActivityInjector
(for AppCompatActivity
, other available for Fragments and Services).
We provide new instance of KodeinInjector
.
And finally, call initializeInjector()
in our onCreate()
and then destroyInjector()
before onDestroy()
.
This is important to make sure we will not introduce memory leaks into our application.
Now we can retrieve our dependencies.
Important note: As we are initializing our Activity in onCreate, we utilize property delegation, to lazyly access Kodein, which is not available at the time of instance creation of our Activity.
Other binding functions
Above we used singleton {}
for providing our TasksRepository
. Now let’s look into other options Kodein has.
Factory
It’s great simplification of classic ‘factory’ pattern. You provide an argument and new instance is created every time.
val kodein = Kodein {
bind<Dice>() with factory { sides: Int -> RandomDice(sides) }
}
The way we retrieve such dependency is using with(6).instance()
. Where the function with()
provides the argument.
Note: if you need to access the argument you are passing lazyly, use the ‘lazy’ version of with { intent.getIntExtra("SIDES") }.instance()
Provider
A factory without an argument, thus, a provider.
val kodein = Kodein {
bind<Dice>() with provider { RandomDice(6) }
}
Singleton
Already mentioned here, on first access invokes the function to obtain instance. That is then persisted during the life of the application.
val kodein = Kodein {
bind<DataSource>() with singleton {
SqliteDS.open("path/to/file")
}
}
There are more of those, the abovementioned are just the most used.
Tagged bindings
Another nice thing use can utilize is tagging your bindings. Sometimes you need more than just one instance of specific service or just multiple bindings. With Kodein it’s very easy, just tag your bindings.
val kodein = Kodein {
bind<Dice>() with factory { sides: Int -> RandomDice(sides) }
bind<Dice>("DnD10") with provider { RandomDice(10) }
bind<Dice>("DnD20") with singleton { RandomDice(20) }
}
The tag doesn’t have to be String, it can be any non-null value.
Java interoperability
Because you may not have 100% pure Kotlin project by now, you may be interested in whether you can use Kodein and thus replace Dagger even with your Java code.
Yes, you can
The author of Kodein thought about us and added Java interoperability layer. You can’t define bindings, but you can retrieve your dependencies!
import static com.github.salomonbrys.kodein.TypesKt.TT; public class JavaClass {
private final Function1<Integer, Dice> diceFactory;
private final Datasource dataSource;
private final Function0<Random> randomProvider;
private final String answerConstant; public JavaClass(Kodein kodein) {
diceFactory = kodein.Factory(
TT(Integer.class),
TT(Dice.class),
null
); dataSource = kodein.Instance(
TT(Datasource.class),
null
);
}}
With the use of static TT()
function, you can retrieve the type argument to access your declared dependencies.
Summary
So I have shown you here an alternative to dependency injection, which heavily utilizes all the ‘cool’ Kotlin features and makes our lives happier.
There is an alternative to KODEIN called KOIN. It’s younger, doesn’t have the Java interoperability part, but on the other hand, it’s much slicker and supports the new ViewModel
from Google out of the box.
In next blog post, I will show some more advanced features of Kodein, and we can also look into KOIN, and it’s comparison with KODEIN.