Skip to main content
Version: 4.2

ViewModel in Compose

This page covers the Compose functions that retrieve a ViewModel: koinViewModel(), sharedKoinViewModel() and koinActivityViewModel(). Declaring a ViewModel in a module, with parameters, SavedStateHandle or a ViewModel scope, is the same on every platform: see ViewModel.

All functions on this page are in io.insert-koin:koin-compose-viewmodel, except sharedKoinViewModel(), which is in io.insert-koin:koin-compose-viewmodel-navigation. See Koin for Compose for the packages.

koinViewModel​

import org.koin.compose.viewmodel.koinViewModel

@Composable
fun UserScreen(viewModel: UserViewModel = koinViewModel()) {
val state by viewModel.state.collectAsState()
// ...
}

koinViewModel() gets the ViewModel from the current ViewModelStoreOwner (LocalViewModelStoreOwner), and creates it with Koin the first time. The instance lives as long as that owner: an Activity, a Fragment, or a navigation destination.

Declaring it as a default parameter keeps the Composable testable: a test or a preview passes its own instance.

koinViewModel() is a Composable function. Call it in the body of a Composable, not in a callback such as onClick.

Parameters​

ParameterDefaultUse
qualifiernullPick a named ViewModel definition
viewModelStoreOwnerLocalViewModelStoreOwner.currentStore the ViewModel in another owner
keynullKeep several instances of the same class in one owner
extrasExtras of the ownerCreationExtras used to build SavedStateHandle
scopecurrentKoinScope()Koin scope used to resolve the ViewModel
parametersnullLambda returning injected parameters

Passing parameters​

@Composable
fun DetailScreen(itemId: String) {
val viewModel = koinViewModel<DetailViewModel> { parametersOf(itemId) }
}

Parameters are used only when the ViewModel is created. If the same owner already holds a DetailViewModel, you get that instance back, even with a different itemId. When one owner must hold one ViewModel per value, add a key:

@Composable
fun DetailScreen(itemId: String) {
val viewModel = koinViewModel<DetailViewModel>(key = itemId) {
parametersOf(itemId)
}
}

With Navigation Compose, each destination has its own owner, so you don't need a key for screens on the back stack.

With Navigation Compose​

Inside a composable { } destination, the current ViewModelStoreOwner is the NavBackStackEntry. koinViewModel() creates one ViewModel per destination, cleared when the destination is popped. Its SavedStateHandle holds the navigation arguments:

@Serializable
data class DetailRoute(val itemId: String)

class DetailViewModel(handle: SavedStateHandle) : ViewModel() {
val itemId: String = handle.toRoute<DetailRoute>().itemId
}

NavHost(navController, startDestination = HomeRoute) {
composable<DetailRoute> {
DetailScreen(viewModel = koinViewModel())
}
}

Koin injects SavedStateHandle for you: declare it as a constructor parameter. See ViewModel for the declaration side.

warning

koinNavViewModel() is deprecated, in both koin-compose-viewmodel-navigation and koin-androidx-compose-navigation. Replace it with koinViewModel() and the same parameters: navigation arguments reach SavedStateHandle the same way.

Sharing a ViewModel in a navigation graph​

sharedKoinViewModel() stores the ViewModel in the back stack entry of the parent graph. Every destination of that graph gets the same instance:

import org.koin.compose.viewmodel.sharedKoinViewModel

navigation<BookGraph>(startDestination = BookList) {
composable<BookList> { entry ->
val shared = entry.sharedKoinViewModel<BookSharedViewModel>(navController)
BookListScreen(shared)
}
composable<BookDetail> { entry ->
val shared = entry.sharedKoinViewModel<BookSharedViewModel>(navController)
BookDetailScreen(shared)
}
}

The parent graph is destination.parent?.route by default. Pass navGraphRoute to target another graph. If the destination has no parent graph, sharedKoinViewModel() falls back to koinViewModel().

Activity-wide ViewModel (Android)​

koinActivityViewModel() stores the ViewModel in the current ComponentActivity. Every Composable of that Activity gets the same instance, whatever navigation destination it is in:

import org.koin.compose.viewmodel.koinActivityViewModel

@Composable
fun CartBadge() {
val cart = koinActivityViewModel<CartViewModel>()
}

@Composable
fun CartScreen() {
val cart = koinActivityViewModel<CartViewModel>() // same instance as in CartBadge
}

It is available on Android only, and fails if LocalActivity is not a ComponentActivity. It takes the same qualifier, key, scope and parameters arguments as koinViewModel().

Scoped dependencies​

A ViewModel can own a scope for its dependencies (viewModelScope { } or @ViewModelScope). Nothing changes on the Compose side: koinViewModel() creates the ViewModel and its scope. The declaration is described in ViewModel.

API summary​

APIOwner of the instancePackage
koinViewModel()LocalViewModelStoreOwner (Activity, Fragment, destination)koin-compose-viewmodel
NavBackStackEntry.sharedKoinViewModel()Parent navigation graph entrykoin-compose-viewmodel-navigation
koinActivityViewModel()Current ComponentActivity (Android)koin-compose-viewmodel
koinNavViewModel()Deprecated, use koinViewModel()koin-compose-viewmodel-navigation