Koin for Kotlin Multiplatform and Compose Multiplatform: Quick Start Guide

Quick start guide on how to add Koin dependency injection for KMP projects, specifically for those using Compose Multiplatform for the view layer.

Koin for Kotlin Multiplatform and Compose Multiplatform: Quick Start Guide

This article will guide you through adding Koin dependency injection for Kotlin Multiplatform (KMP) projects, specifically for those using Compose Multiplatform (CMP) for the view layer.

Koin Dependencies

Adding Koin dependencies to your KMP and CMP project is quite easy as nowadays we have a BOM available. You can check latest version on Maven Central Repository.

Define versions and dependencies in your .toml file

At the moment of writing this article, the latest stable version of the Koin BOM is 4.1.0, but make sure to double check on the maven central repository page.

# your project > gradle > libs.versions.toml

[versions]
koin-bom = "4.1.0"

[libraries]
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" }
koin-core = { module = "io.insert-koin:koin-core" }
koin-compose = { module = "io.insert-koin:koin-compose" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" }

Add dependencies to your build.gradle file

For KMP projects using purely Compose Multiplatform for the UI layer, you only need to add these dependencies into your common source set:

// your project > composeApp > build.gradle.kts
kotlin {
  sourceSets {
    commonMain.dependencies {
      implementation(project.dependencies.platform(libs.koin.bom))
      implementation(libs.koin.core)
      implementation(libs.koin.compose)
      implementation(libs.koin.compose.viewmodel)
    }
  }
}

Define a dependency injection module

To initialise Koin you need to define a dependency injection module. Create a common dependency injection module by simply creating a kotlin file in your commonMain app module and using the Koin DSL:

import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module

val commonModule = module {
    // dependencies will go here
}

Your common dependencies will be added here and we will use this module in the next step for initialising Koin.

Koin Initialisation in KMP and CMP

Once again, initialisation is easier to do for Compose Multiplatform projects. Usually you would have to implement initialisation for each platform you target, but for CMP projects you only need to initialise in a single place.

Open the main App.kt file for your KMP project and wrap your Compose app in the Koin initialisation:

import org.koin.compose.KoinApplication

@Composable
@Preview
fun App() {
    KoinApplication(application = {
        modules(commonModule)
    }) {
        AppTheme {
            // your app composables, navigation host etc.
        }
    }
}

How to inject ViewModels using Koin

Assuming you have a composable screen such as this:

import androidx.compose.runtime.Composable

@Composable
internal fun YourComposableScreenRoot(
    modifier: Modifier,
    viewModel: YourComposableViewModel,
) {
  ...
}

And you've defined a viewModel like so:

import androidx.lifecycle.ViewModel

class YourComposableViewModel : ViewModel() {
  ...
}

You can make the view model injectable by first defining how to inject it via the common module we've declared before:

import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module

val commonModule = module {
    viewModel { YourComposableViewModel() }

    // or constructor DSL
    viewModelOf(::YourComposableViewModel)
}

And then inject an instance using the dedicated Koin function when calling your composable screen in your navigation host or wherever else you are using it:

import org.koin.compose.viewmodel.koinViewModel

YourComposableScreenRoot(
    modifier = Modifier.fillMaxSize(),
    viewModel = koinViewModel<YourComposableViewModel>(),
)

This will work for CMP view models along all targeted platforms.

How to inject ViewModels when using Jetpack Navigation and Koin

Assuming you have the view model setup above and you are using androidx.navigation, you probably have a navigation destination defined like so:

import kotlinx.serialization.Serializable

@Serializable
object YourDestination()

And you probably have a NavHost defined like so:

import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController

@Composable
fun NavigationHost(
    // nav host arguments
) {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = FirstScreen,
    ) {
        // composable destinations go here
    }

To include a Koin view model injection into your composable<> destination definition you can simply use the same koinViewModel<>() call as before:

import androidx.navigation.compose.composable
import org.koin.compose.viewmodel.koinViewModel

composable<YourDestination> {
  YourComposableScreenRoot(
    modifier = Modifier.fillMaxSize(),
    viewModel = koinViewModel<YourComposableViewModel>(), // Koin call here
  )
}

How to inject ViewModels with arguments when using Jetpack Navigation and Koin

Assuming your view model takes arguments:

import androidx.lifecycle.ViewModel

class YourComposableViewModel(
  private val firstArgument: String,
  private val secondArgument: Int,
) : ViewModel() {
  ...
}

First you will have to alter your DI module definition for the view model:

import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module

val commonModule = module {
    viewModel { (firstArg: String, secondArg: Int) ->
        YourComposableViewModel(
          firstArgument = firstArg, 
          secondArgument = secondArg,
        )
    }

    // and other DI definitions
}

Then make sure your navigation destination accepts the same arguments:

import kotlinx.serialization.Serializable

@Serializable
data class YourDestination(
  val firstArgument: String, 
  val secondArgument: Int
)

And finally consume the navigation arguments in your NavHost composable definitions:

import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf

composable<YourDestination> { backStack ->
    // first get typed navigation arguments
    val args = backStack.toRoute<YourDestination>()

    // then get a view model injection with parameters
    val vm: YourComposableViewModel = koinViewModel(
      parameters = { parametersOf(args.firstArgument, args.secondArgument) }
    )

    // finally call the screen root with the injected view model
    YourComposableScreenRoot(
        modifier = Modifier.fillMaxSize(),
        viewModel = vm,
    )
}

And of course, to navigate towards this screen with arguments simply call the navigate function of the nav controller with your desired destination and arguments:

import androidx.navigation.NavController

navController.navigate(YourDestination("Some string", 42))

Bogdan Codes © . All rights reserved.