Skip to main content
Version: 4.2

Koin for Compose

This page covers the Compose packages, the two ways to start Koin in a Compose app, and koinInject() to retrieve dependencies in a Composable. It works the same on Jetpack Compose (Android) and Compose Multiplatform. You should know how to declare definitions and modules.

Packages​

PackageContainsPlatforms
io.insert-koin:koin-composeKoinApplication, koinInject, scope and module APIsMultiplatform
io.insert-koin:koin-compose-viewmodelkoinViewModel, koinActivityViewModel (Android)Multiplatform
io.insert-koin:koin-compose-viewmodel-navigationsharedKoinViewModel, KoinNavigationScope, navigationScopeMultiplatform
io.insert-koin:koin-compose-navigation3Navigation 3 entriesMultiplatform
io.insert-koin:koin-androidx-composekoin-compose + koin-compose-viewmodel, plus KoinActivityScope and KoinFragmentScopeAndroid
io.insert-koin:koin-androidx-compose-navigationDeprecated koinNavViewModel onlyAndroid

All Compose APIs live in the multiplatform packages. koin-androidx-compose is a convenience artifact for Android projects:

// Android app
dependencies {
implementation("io.insert-koin:koin-androidx-compose:$koin_version")
}

// Compose Multiplatform, in commonMain
commonMain.dependencies {
implementation("io.insert-koin:koin-compose:$koin_version")
implementation("io.insert-koin:koin-compose-viewmodel:$koin_version")
}

For the BOM and version catalogs, see Gradle Setup.

Starting Koin​

You have two options. Pick one per app.

Start outside Compose​

Start Koin where your platform starts: the Application class on Android, the shared initKoin() function in a KMP project (see KMP Setup). Compose then uses the running Koin instance with no extra setup:

class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MainApplication)
modules(appModule)
}
}
}

@Composable
fun App() {
val repository = koinInject<UserRepository>()
}

Use this option when code outside Compose (a Service, a WorkManager worker, iOS Swift code) also needs Koin, because that code runs without any Composable.

Start from a Composable with KoinApplication​

KoinApplication starts Koin from the root Composable and provides it to all children:

import org.koin.compose.KoinApplication
import org.koin.dsl.koinConfiguration

@Composable
fun App() {
KoinApplication(
configuration = koinConfiguration { modules(appModule) }
) {
MainScreen()
}
}

What KoinApplication does for you:

  • Platform setup: on Android it calls androidContext() with the application context and sets up androidLogger(). On other platforms it sets up printLogger(). Change the level with the logLevel parameter (default Level.INFO).
  • Existing instance: if Koin is already started, KoinApplication attaches to the running instance and ignores its own configuration.
  • Lifecycle: Koin is not stopped when KoinApplication leaves the composition, so it survives an Android configuration change or Activity recreation. It is stopped only if the composition is abandoned.

koinConfiguration { } builds a reusable KoinConfiguration, so you can share the same configuration between KoinApplication, startKoin and tests.

Deprecated entry points​

APIStatusUse instead
KoinApplication(application = { ... })DeprecatedKoinApplication(configuration = koinConfiguration { ... })
KoinMultiplatformApplication(config)DeprecatedKoinApplication(configuration)
KoinContext { }Deprecated, not neededNothing. Compose finds the instance started with startKoin
KoinAndroidContext { }Deprecated, not neededNothing

Injecting with koinInject​

koinInject() resolves a dependency from the current Koin scope (the root scope, unless a Compose scope is active):

import org.koin.compose.koinInject

@Composable
fun UserScreen() {
val repository = koinInject<UserRepository>()
}

Declare it as a default parameter. The Composable still works with Koin, and a test or a preview can pass its own instance:

@Composable
fun UserScreen(repository: UserRepository = koinInject()) {
// ...
}

koinInject() wraps the resolution in remember, keyed on the qualifier, the scope and the parameters. A recomposition with the same keys returns the same object without resolving again.

Qualifier and scope​

@Composable
fun SyncScreen() {
val localDb = koinInject<Database>(qualifier = named("local"))
val cache = koinInject<SessionCache>(scope = sessionScope)
}

See Qualifiers for declaring named definitions.

Parameters​

Pass injected parameters with a lambda, or with a ParametersHolder:

@Composable
fun UserDetail(userId: String) {
// lambda form
val presenter = koinInject<UserPresenter> { parametersOf(userId) }

// holder form
val presenter2 = koinInject<UserPresenter>(parametersHolder = parametersOf(userId))
}

With the lambda form, Koin invokes the lambda on every recomposition to compare the values. The holder form skips that step, which the Koin sources recommend for performance. Parameters are compared by value: when userId changes, koinInject() resolves a new instance.

Accessing Koin directly​

FunctionReturns
getKoin()The Koin instance of the current composition
currentKoinScope()The scope koinInject() uses by default

Both are Composable functions in org.koin.compose. Prefer koinInject() in UI code.

Previews​

Compose previews do not run your Application class. Use KoinApplicationPreview to give a preview its own Koin instance: see Compose Testing.

API summary​

APIPurposePage
KoinApplication(configuration) { }Start Koin from ComposeThis page
koinInject<T>()Resolve any dependencyThis page
koinViewModel<T>()Resolve a ViewModelViewModel in Compose
KoinScope, KoinNavigationScopeScope bound to a Composable or a destinationScopes in Compose
rememberKoinModules { }Load modules with a ComposableDynamic Modules
KoinIsolatedContextUse an isolated Koin instanceIsolated Context
navigation<T> { }, koinEntryProvider()Navigation 3 entriesNavigation 3