6

Anyone has any clue how to retain a shared view model object across different composables? I'm using hilt and injecting viewmodel instance using hilt in composable. Basically there are 3 screens which share same data and changes I want to share it and I'm thinking of sharing this data through a shared view model.

myViewModel: MyViewModel = hiltViewModel()

So how can i use this MyViewModel as shared view model?

Ali Nawaz
  • 2,016
  • 20
  • 30

3 Answers3

5

All you need is something like this to find the view model in your navigation back stack entry and pass it to next composable screen:

val backStackEntry = remember {
    navHostController.getBackStackEntry("first_screen_route_where_viewmodel_was_firstly_initialized")
}
val viewModel: MyViewModel = hiltViewModel(backStackEntry)

Now you have got the view model which is exactly at same state where you have left it in previous screens. Now you can use it as a shared view model. Thanks @Pylyp for guidance..

william xyz
  • 710
  • 5
  • 19
Ali Nawaz
  • 2,016
  • 20
  • 30
  • 1
    Note that `remember {navController.getBackStackEntry(parentId)}` can cause crashes and now triggers a lint warning ([more here](https://issuetracker.google.com/issues/227382831)). the solution is to use a the `backStackEntry` as a key to `remember` such as. `remember(navBackStackEntry) {navController.getBackStackEntry(parentId)}` – Guerneen4 Oct 06 '22 at 19:59
1

As per the official documentation:

If you need to retrieve the instance of a ViewModel scoped to navigation routes or the navigation graph instead, use the hiltViewModel composable function and pass the corresponding backStackEntry as a parameter:

import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.getBackStackEntry

@Composable
fun MyApp() {
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}
william xyz
  • 710
  • 5
  • 19
gabhor
  • 669
  • 9
  • 23
0

According to @gabhor's answer you can create an extension function to avoid writing the same code everywhere.

@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(navController: NavController): T {
    val navGraphRoute = destination.parent?.route ?: return hiltViewModel()
    val parentEntry = remember(this) {
        navController.getBackStackEntry(navGraphRoute)
    }

    return hiltViewModel(parentEntry)
}

Reference: https://github.dev/philipplackner/NestedNavigationGraphsGuide