Shared ViewModels¶
Sometimes you need to access the same ViewModel instance from several destinations. The library provides multiple ways to achieve this.
Nested navigation¶
The easiest way to share a ViewModel between several destinations is to use a nested NavHost. Simply collect all required destinations into a separate nested NavHost, and pass a ViewModel of the parent entry to each destination.
However, it may not work for all scenarios. Sometimes it is not desirable or possible to group destinations into a single nested NavHost. For such cases, it would be more convenient to use scoping NavHosts.
Scoping NavHosts¶
Each NavHost in the library has its own Scoping
counterpart. For NavHost
it is ScopingNavHost
, for AnimatedNavHost
it is ScopingAnimatedNavHost
, and so on.
Every scoping NavHost gives you the ability to assign scopes to destinations and access scoped ViewModelStoreOwners bound to each of the defined scope. Such scoped ViewModelStoreOwner is created when there is at least one backstack entry marked with the corresponding scope, and removed when there are none of the entries marked with it.
For example, the ViewModelStoreOwner for Scope X will exist only when at least one of the destinations B or C is in the backstack (their positions don't matter). Both B and C can access the same ViewModelStoreOwner instance. A cannot access it as it is not marked with Scope X.
When both B and C are popped off the backstack and there is only A left, the ViewModelStoreOwner for Scope X will be cleared and removed.
Note that if you replace B and C with a new destination D that is also marked with Scope X, the ViewModelStoreOwner will not be recreated, but left as is.
In order to use scoping NavHost, you need to implement NavScopeSpec
and pass it as the scopeSpec
parameter. NavScopeSpec
requests a set of scopes for each destination in the backstack:
@Parcelize
data object ScopeX : Parcelable
val DestinationScopeSpec = NavScopeSpec<Destination, ScopeX> { destination ->
when (destination) {
Destination.B, Destination.C, Destination.D -> setOf(ScopeX)
else -> emptySet()
}
}
Note that a destination may belong to several scopes at once, that's why NavScopeSpec requires you to return a Set
.
Scoped ViewModelStoreOwner is implemented by ScopedNavHostEntry
class. You can acquire all scoped entries associated with the current destination through the ScopingNavHostScope
receiver of the contentSelector
parameter:
ScopingNavHost(
controller = navController,
scopeSpec = DestinationScopeSpec
) { destination ->
when (destination) {
Destination.A -> { /* ... */ }
Destination.B -> {
val sharedViewModel = viewModel<SharedViewModel>(
viewModelStoreOwner = scopedHostEntries[ScopeX]!!
)
}
Destination.C -> { /* same code as for B */ }
Destination.D -> { /* same code as for B */ }
}
}
You just have to pass this ScopedNavHostEntry as the viewModelStoreOwner
parameter of the viewModel
method.
Alternatively, you can access scoped ViewModelStoreOwners through the LocalScopedViewModelStoreOwners
composition local.
Access ViewModels of backstack entries¶
If the two previous solutions are not suitable for your case, you may always access ViewModels of neighbour entries directly. Read more about it here.