Skip to main content
Version: 4.2

ViewModel

This page covers how to declare a ViewModel in a Koin module, on every platform that supports AndroidX ViewModel (Android, iOS, desktop, web). It assumes you know definitions. To retrieve a ViewModel, see the platform pages: Android ViewModel for by viewModel() in an Activity or Fragment, and ViewModel in Compose for koinViewModel().

Dependencies​

The declaration DSL is in koin-core-viewmodel, a multiplatform artifact:

// build.gradle.kts, commonMain
implementation("io.insert-koin:koin-core-viewmodel")

The injection APIs come with the platform artifacts: io.insert-koin:koin-android (it includes koin-core-viewmodel) and io.insert-koin:koin-compose-viewmodel. Versions come from the BOM: see Gradle Setup.

Declaring a ViewModel​

import org.koin.dsl.module
import org.koin.plugin.module.dsl.viewModel

class UserViewModel(private val repository: UserRepository) : ViewModel()

val appModule = module {
viewModel<UserViewModel>()
}

A viewModel definition creates a new instance on each request, like a factory. The platform ViewModelStore (Activity, Fragment, navigation entry) keeps the instance, so the same owner gets the same ViewModel back, across configuration changes on Android. Do not declare a ViewModel as single: it would be shared by every screen and never cleared.

Passing parameters​

A ViewModel often needs a value known only at runtime, such as an item id. Mark the constructor parameter as an injected parameter:

class DetailViewModel(
@InjectedParam val itemId: String,
private val repository: DetailRepository,
) : ViewModel()

val appModule = module {
viewModel<DetailViewModel>()
}

The caller passes the value with parametersOf, whatever the declaration style:

// Android
val viewModel: DetailViewModel by viewModel { parametersOf("item_123") }

// Compose
val viewModel = koinViewModel<DetailViewModel> { parametersOf("item_123") }

Parameters are read once, when the ViewModel is created. If the same owner asks again with another value, it gets the existing instance. More on parameters in Injected Parameters.

SavedStateHandle​

Add a SavedStateHandle parameter to the constructor. Koin creates it from the ViewModel CreationExtras, with no declaration:

class ProfileViewModel(
private val handle: SavedStateHandle,
private val repository: UserRepository,
) : ViewModel() {
val userId: String? = handle["userId"]
}

val appModule = module {
viewModel<ProfileViewModel>()
}

On Android, navigation arguments and Activity extras are in the SavedStateHandle. Inject it in the constructor only. If the ViewModel owner has no SavedStateRegistryOwner, Koin fails with "Koin could not create a SavedStateHandle: the ViewModel's CreationExtras has no SavedStateRegistryOwner."

ViewModel scope​

A ViewModel can own a scope: dependencies created with it and released when it is cleared. Declare them with the viewModelScope archetype:

val appModule = module {
viewModelScope {
scoped<UserCache>()
viewModel<UserViewModel>()
}
}

Enable the option at start, or the ViewModel gets no scope:

startKoin {
options(viewModelScopeFactory())
modules(appModule)
}

viewModelScope and viewModelScopeFactory() require opting in to @KoinViewModelScopeApi and @KoinExperimentalAPI. A ViewModel that takes scoped dependencies in its constructor must be declared inside viewModelScope { }. Each ViewModel instance gets its own UserCache, closed when the ViewModel is cleared.

Limits​

  • One instance per owner: injected parameters are ignored once the ViewModel exists for that owner. Use a different key or owner to get a second instance.
  • No single ViewModel: a singleton ViewModel is never cleared and leaks its dependencies.
  • Experimental ViewModel scope: viewModelScope is marked experimental and may change.