Entry-scoped ViewModels¶
Every unique NavEntry in NavHost provides its own ViewModelStore. Every such ViewModelStore is guaranteed to exist for as long as the associated NavEntry is present in the backstack.
As soon as NavEntry is removed from the backstack, its ViewModelStore with all ViewModels is cleared.
You can get ViewModels as you do it usually, by using composable viewModel
from androidx.lifecycle:lifecycle-viewmodel-compose
artifact, for example:
@Composable
fun SomeScreen() {
val someViewModel = viewModel<SomeViewModel>()
// ...
}
Accessing ViewModels of backstack entries¶
It is possible to access ViewModelStoreOwner of any entry that is currently present on the backstack. It is done through the the NavHostScope
receiver of the contentSelector
parameter of NavHost:
@Composable
fun NavHostScope<Destination>.SomeScreen() {
val previousViewModel = viewModel<PreviousViewModel>(
viewModelStoreOwner = hostEntries.find {
it.destination is Destination.Previous
}!!
)
// ...
}
Passing parameters into a ViewModel¶
There is no such thing as a Bundle of arguments for navigation entries in this library. This means that there is literally nothing to pass into SavedStateHandle
of your ViewModel as the default arguments.
I personally recommend passing all parameters into a ViewModel constructor directly. This keeps everything clean and type-safe.
If you use dependency injections in your project, explore the samples that show how to pass parameters into a ViewModel and inject all other dependencies:
-
Dagger/Anvil/Hilt use @AssistedInject
-
Koin supports ViewModel parameters out of the box and does it charmingly simple
hiltViewModel()¶
If Hilt is the DI library of your choice and you want to use hiltViewModel()
method that you may already be familiar with from the official Navigation Component, you can add the dependency:
implementation("dev.olshevski.navigation:reimagined-hilt:<latest-version>")
It provides a similar hiltViewModel()
method that works with the Reimagined library. The only catch is that by default it doesn't know how to pass arguments to the SavedStateHandle of your ViewModel. For this you can use an additional defaultArguments
parameter:
val viewModel = hiltViewModel<SomeViewModel>(
defaultArguments = bundleOf("id" to id)
)
And in ViewModel you can read this argument as such:
@HiltViewModel
class SomeViewModel @Inject constructor(
savedStateHandle: SavedStateHandle
) : LoggingViewModel() {
private val id: Int = savedStateHandle["id"]!!
}
Tip
Don't forget to annotate your view models with @HiltViewModel
annotation.
Warning
Do not pass mutable data structures as defaultArguments
and expect the external changes to be reflected through the SavedStateHandle inside a ViewModel, e.g. when trying to return results as described here.
As soon as SavedStateHandle parcelize/unparcelize data once, it becomes the only source of truth for the data it holds.
If you still need to pass mutable data structure into your ViewModel, it would be more reliable to pass it directly as a constructor parameter).