ViewModel Scoping APIs Part of Android Jetpack.
Scope is key to using ViewModels effectively. Each ViewModel is scoped to an
object that implements the ViewModelStoreOwner interface. There are
several APIs that allow you to more easily manage the scope of your ViewModels.
This document outlines some of the key techniques you should know.
The ViewModelProvider.get() method lets you obtain an instance of a ViewModel
scoped to any ViewModelStoreOwner. For Kotlin users, there are different
extension functions available for the most common use cases. All Kotlin
extension function implementations use the ViewModelProvider API under the hood.
ViewModels scoped to the closest ViewModelStoreOwner
You can scope a ViewModel to a composable, Activity, or
destination of a Navigation graph. The
viewModel() function in Compose allows you to get an instance of the ViewModel
scoped to the closest ViewModelStoreOwner.
import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( modifier: Modifier = Modifier, // ViewModel API available in lifecycle.lifecycle-viewmodel-compose // The ViewModel is scoped to the closest ViewModelStoreOwner provided // via the LocalViewModelStoreOwner CompositionLocal. In order of proximity, // this could be the destination of a Navigation graph // or the host Activity. viewModel: MyViewModel = viewModel() ) { /* ... */ }
ViewModels scoped to any ViewModelStoreOwner
The viewModel() function takes an optional
viewModelStoreOwner parameter that you can use to specify which
ViewModelStoreOwner the instance of the ViewModel is scoped to.
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.ViewModelStoreOwner @Composable fun MyScreen( // A custom owner passed in, such as a parent NavBackStackEntry customOwner: ViewModelStoreOwner, // The ViewModel is now scoped to the provided customOwner viewModel: MyViewModel = viewModel(viewModelStoreOwner = customOwner) ) { /* ... */ }
ViewModels scoped to a composable
You can use rememberViewModelStoreOwner() to scope a ViewModel directly to
the call site of a composable. This is especially useful for UI components that
are dynamically added to or removed from the screen based on state, such as the
items of a page or lazy list. When the composable that owns the
ViewModelStoreOwner leaves the composition, the associated ViewModelStore
is cleared and the ViewModel is destroyed.
Use rememberViewModelStoreOwner() to create a lifecycle-aware store that
survives configuration changes.
@Composable
fun RememberViewModelStoreOwnerSample() {
// Create a ViewModelStoreOwner scoped to this specific call site.
// When this composable leaves the composition,
// the associated ViewModelStore will be cleared.
val scopedOwner = rememberViewModelStoreOwner()
CompositionLocalProvider(LocalViewModelStoreOwner provides scopedOwner) {
// This ViewModel is scoped to `scopedOwner`.
// It will survive configuration changes but will be cleared when
// the composable is removed from the UI tree.
val viewModel = viewModel { TestViewModel("scoped_data") }
// Use the ViewModel
}
}
For more complex implementations, such as a HorizontalPager or cases
requiring multiple independent scopes, use rememberViewModelStoreProvider().
This lets you generate distinct ViewModelStoreOwner instances for different
keys (like page indices). This way, each page maintains its own independent
ViewModel state.
@Composable
fun RememberViewModelStoreProviderSample() {
val storeProvider = rememberViewModelStoreProvider()
val pages = listOf("Page 1", "Page 2", "Page 3")
HorizontalPager(pageCount = pages.size) { page ->
// Create a ViewModelStoreOwner for the specific page using the provider.
val pageOwner = rememberViewModelStoreOwner(provider = storeProvider, key = page)
CompositionLocalProvider(LocalViewModelStoreOwner provides pageOwner) {
val pageViewModel = viewModel { TestViewModel(pages[page]) }
// Use pageViewModel
}
}
}
ViewModels scoped to the Navigation graph
Navigation graphs are also ViewModel store owners. If you're using
Navigation Compose, you can get an instance of a
ViewModel scoped to a Navigation graph with the
getBackStackEntry() function.
viewModel() retrieves the instance from the nearest ViewModelStoreOwner
provided by the LocalViewModelStoreOwner CompositionLocal. In a typical
Compose application using Jetpack Navigation, this owner is the current
Navigation back stack entry. This means the ViewModel remains in memory as long
as that destination is present in the back stack.
import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyAppNavHost() { // ... composable("myScreen") { backStackEntry -> // Retrieve the NavBackStackEntry of "parentNavigationRoute" val parentEntry = remember(backStackEntry) { navController.getBackStackEntry("parentNavigationRoute") } // Get the ViewModel scoped to the `parentNavigationRoute` Nav graph val parentViewModel: SharedViewModel = viewModel(parentEntry) // ... } }
If you're using Hilt in addition to Jetpack Navigation, you can use the
hiltNavGraphViewModels(graphId)
API as follows.
import androidx.hilt.navigation.compose.hiltViewModel @Composable fun MyAppNavHost() { // ... composable("myScreen") { backStackEntry -> val parentEntry = remember(backStackEntry) { navController.getBackStackEntry("parentNavigationRoute") } // ViewModel API available in hilt.hilt-navigation-compose // The ViewModel is scoped to the `parentNavigationRoute` Navigation graph // and is provided using the Hilt-generated ViewModel factory val parentViewModel: SharedViewModel = hiltViewModel(parentEntry) // ... } }
Additional resources
To learn more about ViewModels and scope, see the following additional resources: