Dependency Injection on Kotlin/Multiplatform — Part 1: Factorize your code

Romain Boisselle
Kodein Koders
Published in
5 min readJun 22, 2020

This article is part of a series about using dependency injection on Kotlin/Multiplatform:

Kotlin/Multiplatform is certainly on the 2020’s list of programming trends. For sure, it’s a very nice technology, and it is now pretty mature. However, when starting using something new, it’s also convenient to have a bunch of tools that make our day to day job easier. Kotlin/Multiplatform journey is at its beginning, when it comes to tooling, there are some few, but there are still some topics no covered yet, and we are excited to see what’s coming in the next few months.

Until then, this is the first article of a series, that will guide you through the use of our Kotlin/Multiplatform Dependency Injection library: Kodein-DI.

In this part, we will see what we’ll be building, how to configure your Kotlin/Multiplatform project, and how to use Kodein-DI in this particular use case.

As with my wife we are close to having our second child, I have decided to find some time to build an app for our friends and families to bet on the name, the date and some other data regarding the birth of the new kid 😃

Here the data classes of the app:

So the idea is quite simple. We need to provide an app to enter those prognostics information and send them to a server. But, among our friends and families, there is both Android and iOS devices… Let’s try to factorize and test our business code in one place before using it on native platforms.

I am moving fast over the configuration of our Kotlin/Multiplatform project, as many resources have already covered this part:

What will matter to us, is how do we manage dependencies for Kotlin/Multiplatform projects, and more precisely for Kodein-DI.

Thanks to the Gradle Metadata feature, we do not have so much to do while targetting different platforms, but just declaring the use of Kodein-DI in the common source set, as long as you are using Gradle 6+.

In fact, when we build Kodein-DI, on MacOS for example, Gradle will produce binaries for each targetted platform, like MacOS / iOS / JVM / JS. But, it will also produce some files that are carrying references on which binaries are needed depending on the target. So, in the case of androidMain , Gradle will get org.kodein.di:kodein-di-jvm .

Note: there are Android-specific Kodein-DI modules that we will cover in a future part.

As we do not want the entire earth to be able to vote, I put a simple PIN-based authentication. This auth is verified by the server and returns a token, stored on the device. Here is the definition:

What's important here is the fact that most of the classes have interfaces. That way, implementations will be swappable with fake objects for testing, by using Kodein-DI.

To interact with the UI, thus the native code, I’ve chosen to use the MVP pattern, as I find it simple to grasp and to use for my use case. Thus, I have 2 interfaces, one for the Presenter and one for the View, that should be implemented or used by my native code somehow.

The Login.Presenter can receive instruction to authenticate the user with a secret PIN or to check the current state of the authentication.

The Login.View can respond to the UI with a onLoginSuccess function, but also with a onError, available from its parent interface BaseView.

You may have noticed that LoginPresenterImpl is implementing the interface DIAware. This provides a global approach for the class to use the dependency injection. Because of the interface, we can smoothly retrieve our dependency bindings as delegated properties, using by instance(). Doing so, the AuthApi and TokenRepository should be retrieved seamlessly, without the need to know how to instantiate them, at call-site.

This article is all about managing our dependency injection while using Kotlin/Multiplatform, remember?. So, let’s see how to define our dependencies.

To be able to test efficiently my business code, I decided to split concerns in modules. Kodein-DI's modules provide a way to extract our bindings in separated objects, avoiding one big central DI container, containing every binding of our application. Those modules can be imported side by side in a global container.

With this approach, I should be able to test atomically my code, or even swap an entire module if needed. Here is my login module:

So you’ve read so far, cool! Here is another example, based on the Participant API, used to give to the users the ability to send prognostics, change them, and see what they already have sent.

And here is the corresponding Kodein-DI module definition:

Now that we have defined our APIs and our DI modules, we will need to set on a dependency injection container, that will carry every binding that we would be using in our application.

Note: We didn’t talk about the database layer, we will certainly do in some next articles ;)

So, every module is imported in a global container. Usable in any native code, almost…

At the moment, Kodein-DI (v7.0) is Kotlin/Multiplatform, but, as it will mainly be a dependency for another K/M project, the API would less usable from non-kotlin native code, like Swift. Indeed, Swift does not support things like delegation properties or reified parameters, making the use of instance<T>() function impossible. So, for now, the solution is to give the ability to swift to call out our dependencies through facades:

Don’t worry, we are working on something that will make Kodein-DI as easy to use from Swift than from Kotlin 🙂

Here we have defined how to bootstrap our Kotlin/Multiplatform project using Kodein-DI. Next time we will see how we can test our code and how to bundle .jar and .framework to be used on Android and iOS.

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

--

--