Building & Deploying a simple KMP app — Part 4: Dependency injection

Using Koin for multi-platform dependency injection.
In this series I will go through build and deployment of a simple property website in Kotlin multi-platform. The aim is to cover more of the platform dependent issues and provide an example of a fully deployed app over Android, iOS, Desktop and Web.
The Github repository is here: https://github.com/sentinelweb/house-shoreditch
In this series:
- App icons
- Secrets
- Launchers
- Dependency injection
- CI — Develop
- CI — Release Builds
- Deployment
Introduction
In a Kotlin Multiplatform (KMP) app, managing dependencies efficiently is crucial to ensure modularity, testability, and maintainability across shared and platform-specific code. Dependency Injection (DI) simplifies this by providing a structured way to manage object creation and lifecycle, reducing tight coupling and making the app easier to scale and test.
We use the Koin framework for our simple app. It’s a great DI framework and very easy to work with. Even though the app requirements are simple I will cover basic setup and also some issues I found while implementing DI.
Setup
Common
For everything in our composeApp setup is very easy — we declare modules in our commonMain
object OasisModules {
// default koin init - desktop/WASM
fun initKoin() {
startKoin {
modules(allModules)
}
}
private val mainModule = module {
viewModel {
MainViewModel(
launcher = get(),
secrets = Secrets
)
}
}
private val utilModule = module {
factory { MessageMapper() }
factory { Launcher() }
}
val allModules = listOf(mainModule, utilModule)
}
Desktop/WASM
The initKoin() method is the default initialization method that gets called from the main() methods for wasm and desktop.
Android
For Android we make a separate initialization object that we call in the Application class — so we can also inject the androidContext(). There are also a couple of android specific implementations— so we add those to the modules list.
class OasisApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@OasisApp)
modules(
OasisModules.allModules
.plus(AndroidModules.androidModule)
)
}
}
}
iOS
We start Koin from the UIApplicationDelegate
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IosKoinConfig.shared.startKoin()
return true
}
There are some additional iOS modules:
object IosKoinConfig {
fun startKoin() {
startKoin {
modules(
OasisModules.allModules + listOf(iosControllerModule, iosLauncherModule)
)
}
}
private val iosControllerModule = module {
single<UIViewControllerHolder> { UIViewControllerHolder() }
}
private val iosLauncherModule = module {
factory { EmailLauncher(get()) }
factory { SMSLauncher(get()) }
}
}
We also have factories for the EmailLauncher and SMSLauncher (see post 3). These both require a reference to the root UIViewController which I have made a holder object for. This is probably overkill — but I didn’t like holding a singleton reference to this UIViewController. So the holder keeps a kotlin.native.ref.WeakReference (as kotlin/native implements its own memory handling). Then I can inject the UIViewControllerHolder from Koin and use it to create, get or destory the UIViewController as needed.
@OptIn(ExperimentalNativeApi::class)
class UIViewControllerHolder() {
private var viewControllerRef: WeakReference<UIViewController>? = null
val viewController: UIViewController? get() = viewControllerRef?.get()
fun createViewController(): UIViewController {
MainViewController().apply {
viewControllerRef = WeakReference(this)
return@createViewController this
}
}
fun cleanupViewController() {
viewControllerRef?.clear()
viewControllerRef = null
}
}
There are a few differences for iOS as we cannot directly inject Swift classes due to this bug. Basically there is no KClass object for Objective-C objects so they cant be referenced. They way around this is the make a static factory object that can provide dependencies to our iOS app:
object IosClassFactory : KoinComponent {
fun getViewControllerHolder() = getKoin().get<UIViewControllerHolder>()
}
Since this factory implements KoinComponent we can use Koin injection to provide dependencies. We use it to actually get our UIViewControllerHolder and create the UIViewController which get return to the system.
struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return IosClassFactory.shared.getViewControllerHolder().createViewController()
}
}
While this all looks a bit cumbersome — it actually isn’t too bad in practice as most of our code is in commonMain. Especially since we have compose for UI.
Some other notes on using Koin with iOS:
- If we need to provide any objects/or interfaces from Swift to Kotlin we can pass them when we start Koin — we can implement Kotlin interfaces in swift which allows us to call any functionality needed.
- Using flows and co-routines from Kotlin can be help greatly by using the rickclephas / KMP-NativeCoroutines library.
- There is a helpful iOS DI cheat sheet on the Koin website.
ViewModels
We are using the Jetbrains ViewModel object — since we only have one view model in this application (as is a single long scrolling layout with mostly static data). We can just use koinViewModel() to provide out view model.
@Composable
fun App(viewModel: MainViewModel = koinViewModel()) {
}
Conclusion
Dependency Injection is essential for any commercial project — Koin is very useful and easy to configure. It’s usage is very easy for most platforms, there are a few complication on iOS but they can be worked around with a bit of thought, actually the native iOS DI options aren’t that great either. So using Koin is a win.
Next: Continuous Integration …
Building & Deploying a simple KMP app — Part 4: Dependency injection was originally published in ProAndroidDev on Medium, where people are continuing the conversation by highlighting and responding to this story.




This Post Has 0 Comments