Dependency Injection for Kotlin/Multiplatform

Romain Boisselle
Kodein Koders
Published in
4 min readFeb 24, 2021

In this article, we will see how to use a dependency injection library in your Kotlin/Multiplatform projects. I will walk you through the basics of the dependency injection pattern, and the materials we have at our disposal in a Kotlin/Multiplatform context, using the Kodein-DI library.

First, let me remind you why you should consider using dependency injection, and what are the benefits of a DI library. In software development, using dependency injection is the most popular technique to address the principle of separation of concerns. Its goal is to free objects of any responsibility but their original purpose. Let’s take an example of an application working with users, that is capable of retrieving data from an HTTP API, and also works offline with a local database. Here is the code we could write to make such an application:

Here, the different classes are responsible for their functions’ behaviour, but they also are responsible for creating the objects they need to work properly.

A more idiomatic design would be to separate all the objects’ creation from their call site, by defining them as external resources, for instance as constructor parameters.

This removes the responsibility of creating dependencies, enables code reusability, and should help you mock and test your code easily.

Now, the simplest thing you could do is to manage your dependencies manually, to instantiate and inject them in a global context, accessible from anywhere in your application.

However, this comes with a cost. Testing the different units becomes a challenge, because our instances are tightly coupled with the Injector object. This means that we’ll have to know how to instantiate our objects and dependencies, and how they are used, before plugging everything manually. This is where a Dependency Injection library helps.

A DI library gives you the tools to define the dependency graph of your application, and will be able to dynamically inject the right dependency when needed. It also gives the ability to swap object’s instances, and replace them with different implementations in an easy and readable way, which removes a lot of pain points when testing your code.

So now, let’s see how to configure and use Kodein-DI, our favorite dependency injection library for Kotlin/Multiplatform.

Kodein-DI has never been easier to use and configure in KMPs. All you need to do is to define a dependency in the common source set of your Gradle build file, and you’re done. Thanks to the Gradle Metadata published along with all the Kodein-DI binaries, Gradle will be able to attach the right binaries for each target of your project.

NB: we highly recommend to use Gradle 6.0, or higher, which enables Gradle Metadata by default.

To see how to use Kodein-DI let’s take our last example, where every class receives its dependencies as constructor parameters.

In our common source set, we can define a DI container that will provide all the needed dependencies. Thus, we can bind straight instances, singletons or even constants.

There are some other use cases where Kodein-DI can shine. For example, let’s say that you want to handle multiple databases, you could use tags to bind multiple instances of the DB object, and use those tags to inject the proper instance when needed.

We are also able to bind more volatile objects through providers and factories. A factory will be executed every time the DI container asks for it. When using a factory you’ll have to pass arguments to the instance function to get its result.

If you don’t want to bother managing your dependencies via your constructors, maintaining an API, a certain order in the parameters, and so on, you can pass around the DI container while writing your bindings. Therefore, you should be able to call every binding from that container.

We can go further by using the `DIAware` interface. Every object that implements `DIAware` needs to override a `di` property as a `DI` container reference. This interface provides shortcuts to the container, like the `instance` delegate.

When declaring your bindings, all you have to do is to pass the DI container itself within your binding declaration.

When working on bigger projects, We recommend to split your DI container into modules, to organise your code, or even be able to swap an entire block of dependencies. For example we could define a module for the app utilities, and one for the user business code, before importing them in a DI container.

Now that we know how to bootstrap Kodein-DI in a Kotlin/Multiplatform project, let’s see how we can easily swap our bindings and test our code. With Kodein-DI, every dependency is resolved lazily when needed. This means that you don’t need to mock an entire module if its bindings aren’t useful for your test case.

For example, we can define a module “Mock/User”, that overrides our module “App/User” while importing them in a local DI container. Now our UserService object uses the mocks `MockUserRepository` and `MockUserApi` internally, meaning we can focus on unit testing the behaviours of UserService.

In this article we explained what is the DI pattern and how to solve it with Kodein-DI, in a Kotlin/Multiplatform context. Now what remains is to use our business shared code within your favorite platforms. We will cover this in further videos for both Android and iOS, with some architectural approaches.

That’s it folks! Thanks for reading, see you next time for more content on Kodein-DI.

This article is brought to you by Kodein Koders, a European service & training provider focused on Kotlin & Kotlin/Multiplatform.

--

--