Dependency Injection in Kotlin Multiplatform (KMP) with Koin
![Dependency Injection in Kotlin Multiplatform (KMP) with Koin](/content/images/size/w1462/2024/07/cover.webp)
As a mobile developer Dependency Injection (DI) is a design pattern in software engineering where an object's dependencies are provided (or "injected") by an external entity rather than the object creating the dependencies itself. This is typically achieved through a constructor, setter, or interface injection. The main idea is to separate the creation of an object's dependencies from the object's behavior, promoting a more modular and testable codebase.
Let's start with some key fundamentals before digging into this topic. Still, it won't be long as many of you will be familiar with mobile development and dependency injection.
So why Koin?
Besides Koin supporting KMP environment, it provides an easy and efficient way to incorporate dependency injection into any Kotlin application (Multiplatform, Android, backend ...)
The goals of Koin are:
- Simplify your Dependency Injection infrastructure with smart API
- Kotlin DSL is easy to read, easy to use, to lets you write any kind of application
- Provides different kinds of integration from the Android ecosystem, to more backend needs like Ktor
- Allow to be used with annotations
AND THAT'S SOME KIND OF INFORMATION THAT YOU COULD FIND EASILY ON THE INTERNET.. I know it's boring...
Enough talk and let's start the exciting part where you can see the magic 😃
For this example, I'll be using PokéAPI to display a list of Pokemon's
First Start by creating your project over
![](https://www.oaniba.com/content/images/2024/06/Screenshot-2024-06-30-at-20.40.05.png)
Leave the configuration as it is, only specify your project name and package name (Project ID), and then open it using your Android Studio (Mine is: Android Studio Koala | 2024.1.1)
![](https://www.oaniba.com/content/images/2024/06/Screenshot-2024-06-30-at-22.42.07.png)
After opening the project I cleaned the boilerplate code
let's start by importing packages and dependencies that we need, go to
gradle/libs.version.toml and add the following
Under [versions] add :
ktor-client = "2.3.11"
lifecycleViewModel = "2.8.2"
koin = "3.6.0-alpha3"
koinCompose = "3.6.0-alpha3"
koinComposeMultiplatform = "1.2.0-alpha3"
navigationCompose = "2.7.0-alpha03"
Under [libraries] add :
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-client" }
ktor-client-encoding = { module = "io.ktor:ktor-client-encoding", version.ref = "ktor-client" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor-client" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor-client" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor-client" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor-client" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor-client" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinCompose" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koinComposeMultiplatform" }
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "lifecycleViewModel"}
Under [plugins] add :
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Go to the APP level gradle file and add the following
First of all add under plugins
alias(libs.plugins.kotlin.serialization)
Under sourceSets > androidMain.dependencies :
implementation(libs.ktor.client.okhttp)
implementation(libs.koin.android)
implementation(libs.koin.androidx.compose)
Under sourceSets > commonMain.dependencies :
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.encoding)
api(libs.koin.core)
implementation(libs.koin.compose) implementation(libs.navigation.compose)
implementation(libs.lifecycle.viewmodel)
and then add the IOS part as follows:
iosMain.dependencies {
implementation(libs.ktor.client.darwin) // added
}
Hit sync now and wait, once finished without errors, let's make sure we keep in mind some key conditions in mind
Upon creating modules we first need to know about the expect/actual implementation
Rules for expected and actual declarations
To define expected and actual declarations, follow these rules:
- In the common source set, declare a standard Kotlin construct. This can be a function, property, class, interface, enumeration, or annotation.
- Mark this construct with the
expect
keyword. This is your expected declaration. These declarations can be used in the common code, but shouldn't include any implementation. Instead, the platform-specific code provides this implementation. - In each platform-specific source set, declare the same construct in the same package and mark it with the
actual
keyword. This is your actual declaration, which typically contains an implementation using platform-specific libraries.
Now let's set what we need for HTTP calls, ViewModels, and Koin initialization
Under common main add the following files, preferably following the same architecture unless you know how to adapt the code based on your needs, the same structure must be added under androidMain and iosMain, and file nomination and path must be identical on all parts
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.19.44-1.png)
[module] package folder contains the following:
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.24.28.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.24.34.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.24.38.png)
Expects
[repository] package folder contains the following:
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.26.14.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.26.17.png)
[source] package folder contains the following:
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.26.20.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.26.22.png)
[models] package folder contains the following:
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-20.05.52.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-20.05.49.png)
this data class has been generated using JSON to Kotlin class plugin in Android Studio, you can see the actual data that we will be using Pokémon API
Now let's see what's under the hood for androidMain and iosMain folder
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.36.07.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.37.56.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.37.53-1.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.39.36.png)
androidMain
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.42.41.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.41.30.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.41.33.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-01.41.36.png)
iosMain
Extra configuration (Android only)
Let's define our Application entry and initialize Koin first
under androidMain > kotlin > com.your.package-name > create a file/class PokeApplication and set-up the following
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-19.19.36.png)
Add Internet permission and name attribute into AndroidManifest.xml
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-19.20.03.png)
Hurraaaaaaah!! you made it, start your project, and hopefully, you won't face any issues. if so let's take a look inside our viewModel and display some quick information, nothing much just a list that shows what we got through the Poké API
PS: I didn't mind much about the design or the design pattern or even the architecture the sole reason for this article is to show you how things are done with KMP
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-20.03.59-1.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-20.03.51-1.png)
![](https://www.oaniba.com/content/images/2024/07/Screenshot-2024-07-01-at-20.03.43-1.png)
I won't be explaining much about the UI or the business logic as I presume that if you are reading this article you know enough about Jetpack Compose principle and Kotlin
You can find the full source code through my GitHub Repository
Hope this can help you through your learning journey as I am still learning
Enjoy!
References:
![](https://kotlinlang.org/assets/images/open-graph/docs.png)
THIS DUDE IS AWESOOOOOOOME !
Oussama Aniba Newsletter
Join the newsletter to receive the latest updates in your inbox.