Reputation: 488
Consider this example.
For authentication, we'll be using 2 screens - one screen to enter phone number and the other to enter OTP.
Both these screens were made in Jetpack Compose and the for the NavGraph, we are using compose navigation.
Also I have to mention that DI is being handled by Koin.
val navController = rememberNavController()
NavHost(navController) {
navigation(
startDestination = "phone_number_screen",
route = "auth"
) {
composable(route = "phone_number_screen") {
// Get's a new instance of AuthViewModel
PhoneNumberScreen(viewModel = getViewModel<AuthViewModel>())
}
composable(route = "otp_screen") {
// Get's a new instance of AuthViewModel
OTPScreen(viewModel = getViewModel<AuthViewModel>())
}
}
}
So how can we share the same viewmodel among two or more composables in a Jetpack compose NavGraph?
Upvotes: 28
Views: 14882
Reputation: 31
Its simplest and efficient way to handle this
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
navController: NavController
): T {
val navGraphRoute = destination.parent?.route ?: return ViewModel()
val parentEntry = remember(this) {
navController.getBackStackEntry(navGraphRoute)
}
return ViewModel(parentEntry)
}
Then call it from composefun
composable("route") {
it.sharedViewModel<ThoughtViewModel>(navController = navController).apply {
RequestsScreen(this@apply)
}
}
Upvotes: 2
Reputation: 825
Here is an other way with Koin.
It strictly do the same than the validated answer but simpler to write. It will have exactly the same viewModelStoreOwner without having to write it explicitly. Please tell me if i'm wrong.
val navController = rememberNavController()
val sharedViewModel = getViewModel()
NavHost(navController = navController, startDestination = "first") {
composable("first") {
// You can use sharedViewModel
}
composable("second") {
// You can use sharedViewModel
}
}
Upvotes: 0
Reputation: 2020
Using Hilt you could do something like the below. But since you are using Koin I don't know the way of Koin yet.
@Composable
fun MyApp() {
NavHost(navController, startDestination = startRoute) {
navigation(startDestination = innerStartRoute, route = "Parent") {
// ...
composable("exampleWithRoute") { backStackEntry ->
val parentEntry = remember {
navController.getBackStackEntry("Parent")
}
val parentViewModel = hiltViewModel<ParentViewModel>(
parentEntry
)
ExampleWithRouteScreen(parentViewModel)
}
}
}
}
Official doc: https://developer.android.com/jetpack/compose/libraries#hilt
Upvotes: 14
Reputation: 87774
You can to pass your top viewModelStoreOwner
to each destination
.viewModel()
call, composable("first")
in my exampleLocalViewModelStoreOwner
for the whole content, so each composable inside CompositionLocalProvider
will have access to the same view models, composable("second")
in my exampleval viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "first") {
composable("first") {
val model = viewModel<SharedModel>(viewModelStoreOwner = viewModelStoreOwner)
}
composable("second") {
CompositionLocalProvider(
LocalViewModelStoreOwner provides viewModelStoreOwner
) {
SecondScreen()
}
}
}
In the second case, you can get your model at any level of the composition tree, which is inside the CompositionLocalProvider
:
@Composable
fun SecondScreen() {
val model = viewModel<SharedModel>()
SomeView()
}
@Composable
fun SomeView() {
val model = viewModel<SharedModel>()
}
Upvotes: 31