Dependency Injection using Koin in Kotlin Multiplatform Mobile (KMM)

Karim Reda
arconsis
Published in
6 min readJul 26, 2023

Kotlin Multiplatform Mobile (KMM) is gaining popularity among mobile developers for its ability to share code between different platforms. It allows developers to write business logic once and share it across Android, iOS, and other platforms, reducing development time and effort. One crucial aspect of any mobile application development is managing dependencies, and KMM provides a powerful dependency injection framework called Koin.

What is Dependency Injection?

Dependency Injection is a software design pattern that aims to decouple the components of an application and manage their dependencies in a flexible and modular way. In this pattern, the responsibility of creating and providing dependencies is moved from the dependent component to an external entity.

What is Koin Framework?

Koin is a lightweight dependency injection framework for Kotlin that simplifies the process of managing dependencies in your KMM project. It enables you to decouple your code and makes it easier to test and maintain. In this article, we will explore how to use Koin for dependency injection in KMM.

Advantages of Dependency Injection with Koin in KMM

  1. Decoupling and Modularization: Koin helps decouple your application’s components by providing a clear separation between the creation of dependencies and their usage. This promotes modularization, allowing you to develop and maintain individual components independently. By reducing the tight coupling between classes, your code becomes more flexible, easier to understand, and less prone to breaking when modifications are made.
  2. Code Reusability: Koin enables you to define and manage dependencies in a shared module that can be utilized across different platforms in your KMM projects, such as Android and iOS.
  3. Testability: With Dependency Injection using Koin, you can easily mock dependencies during unit testing. Koin provides a convenient way to replace actual implementations with test doubles, allowing you to isolate components and focus on testing specific behaviors without relying on real dependencies.
  4. Runtime Flexibility: Koin allows you to define dependencies and their implementations at runtime, rather than compile time. This flexibility is particularly valuable in scenarios where dependencies need to be resolved dynamically based on specific conditions or configurations.
  5. Lightweight and Simple: Koin is a lightweight dependency injection framework with a simple and intuitive API. It has a minimal learning curve, making it easy to get started and integrate into your KMM projects. Koin leverages the power of Kotlin’s language features, such as extension functions and DSLs (Domain-Specific Languages), to provide a concise and expressive syntax for defining dependencies.
  6. Integration with Native Platforms: Koin seamlessly integrates with the native frameworks of Android and iOS within KMM. You can initialize and configure Koin within the respective platform-specific entry points, such as the Application class in Android and the AppDelegate class in iOS. This ensures consistency and ease of use across platforms, allowing you to leverage the benefits of dependency injection in a platform-agnostic manner.

Implementing Koin into a KMM Project

To incorporate Koin into your KMM project, follow the steps outlined below.

Step 1: Set up your KMM project

Create a new KMM project using the instructions provided by JetBrains. Ensure you have the necessary project structure and configuration for iOS and Android platforms.

If you’re unfamiliar with the process of creating a KMM project, we encourage you to refer to our article on getting started with Kotlin Multiplatform Mobile (KMM). It provides detailed guidance on the topic.

Step 2: Add Koin to your project dependencies

  1. Shared Module
    In your KMM project, add the Koin dependency to your shared module’s build.gradle file.
kotlin {
android()
ios()
}
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.insert-koin:koin-core:$koin_version")
implementation("io.insert-koin:koin-test:$koin_version")
}
}
}
}

Replace <koin_version> with the latest version of the Koin library. Once you have added the dependencies, you can start using Koin in your shared module.

2. Android Project
In your Android Module project, add the Koin dependency to your Android module’s build.gradle file.

dependencies {
implementation("io.insert-koin:koin-android:$koin_android_version")
implementation("io.insert-koin:koin-androidx-compose:$koin_android_compose_version")
}

Replace <koin_android_version> & <koin_android_compose_version> with the latest version of the Koin library.

Step 3: Defining Koin Dependencies

Koin allows you to perform dependency injection not only within the shared module but also within the Android project itself. This means you can also utilize the Koin framework natively within your Android project. Therefore, you will define the dependencies for the shared module within the shared module itself, while the dependencies specific to the Android app will be defined within the Android module.

In Koin, dependencies are defined using modules. A module contains the definitions of dependencies and how to create instances of them. You can define a module using the module function and provide a set of dependencies using the single or factory functions.

For example, let’s say we have a UserService interface and its implementation UserServiceImpl inside the shared module. We can define these dependencies by creating a new Kotlin file named “Koin.kt” inside the shared module and define the dependency module as follows:

fun appModule() = module {
// appModule: refers to Shared module dependencies
single<UserService> { UserServiceImpl() }

}

Here, we are defining a single instance of UserService using the single function. Whenever an instance of UserService is required, Koin will provide an instance of UserServiceImpl.

Step 4: Starting Koin for each platform

  1. Android Platform:
    We need to start Koin with our Android application. Just call the startKoin() function in the application’s main entry point, our MainApplication class:
class App : Application() {
val androidModule = module {
// Android-specific dependencies

}

override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MainApplication)
// androidModule: refers to Android-specific
dependencies
// appModule: refers to Shared module dependencies
modules(appModule() + androidModule)
}
}
}

2. iOS Platform:
To initiate Koin in the iOS application, we create a function in the shared module, this function allows us to configure Koin and set up default data.

Create a new Kotlin file named “HelperKt.kt” inside the shared module and define initKoin() function as follows:

fun initKoin() {
// start Koin
startKoin {
modules(appModule())
}
}

Finally, in the iOS main entry, we can call the HelperKt.doInitKoin() function that is called our helper function above.

@main 
struct iOSApp: App {
init() {
HelperKt.doInitKoin()
}
//…
}

Step 5: Injecting Dependencies in Android

There are two ways to inject dependencies into your class:

  1. Constructor Injection:
    By declaring a dependency in your class constructor, you can inject it effortlessly. However, in this scenario, it is essential to inform Koin about the instantiation of your class by defining it within the dependency module.

    For example, let’s say we have a UserViewModel class that requires an instance of UserService. We can inject it as follows:
class UserViewModel: ViewModel(private val userService: UserService) {
//…
}

But you also need to instruct Koin on how to create an instance of the UserViewModel. Knowing that UserService is already a dependency declared in the shared module.

class App : Application() {
val androidModule = module {
// Android-specific dependencies
viewModel { UserViewModel(get()) }

}

override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MainApplication)
// androidModule: refers to Android-specific dependencies
// appModule: refers to Shared module dependencies
modules(appModule() + androidModule)
}
}
}

Here, UserViewModel will be automatically initialized with the userService instance provided by Koin.

2. Property Injection:
To inject dependencies into your classes, you can use the ‘by inject()’ delegate provided by Koin. You simply declare a property with the desired type and use the ‘by inject()’ delegate to initialize it.

For example, let’s say we have a UserViewModel class that requires an instance of UserService. We can inject it as follows. Knowing that UserService is already a dependency declared in the shared module.

class UserViewModel : ViewModel() {
private val userService: UserService by inject()
//…
}

Here, userService will be automatically initialized with the instance provided by Koin.

Step 6: Injecting Dependencies in iOS

To utilize Koin in the iOS platform, you are required to create a helper class to work as a bridge between the shared module and the iOS app, where you could bootstrap Koin dependencies:

/**
* Needed only for iOS to be able to use Koin-injected classes
*/
class DIHelper : KoinComponent {
val userService: UserService by inject()

}

That’s it, you can now just call userService from the iOS part.

import shared

struct ContentView: View {
let user = DIHelper ().userService().getUser()
var body: some View {
Text(user.username)
}
}

Conclusion

Dependency injection plays a vital role in building maintainable and testable applications. With Koin in Kotlin Multiplatform Mobile (KMM), you can leverage the power of dependency injection to manage dependencies effectively. In this article, we explored how to define dependencies using modules, inject them into your classes, and integrate Koin with Android and iOS platforms.

By adopting Koin in your KMM projects, you can achieve better separation of concerns, improved code quality, and easier testing. With its lightweight and intuitive API, Koin is a valuable tool for managing dependencies in Kotlin Multiplatform Mobile development.

--

--