How can I collect, observe changes from savedState...
# compose-android
b
How can I collect, observe changes from savedStateHandle in a viewModel? I have Screen A and ScreenAViewModel, I navigate to screen B, then press a button which takes the navController.previousBackStackEntry and updates a key value, the Compose savedStateHandle from the backStackEntry gets updated, but the savedStateHandle in the viewModel does not. I tried using the StateFlow on the savedStateHandle and the update never triggers a collection, I tested changing the key in the viewModels savedStateHandle and that does work, but the problem is that I'm trying to change it from the other screen which is outside of the scope of ScreenAViewModel. Any ideas? I'm basically trying to pass an argument back from Screen B, which is an id, and then trigger a network call to get some data. (think going from a form, to an item or location picker). Should I just get it from the backStackEntry in the composable, and then trigger a LaunchedEffect when it changes instead of trying to observe changes in the viewModel?
Copy code
viewModelScope.launch {
    savedStateHandle.getStateFlow(key = "customerId", initialValue = null as String?)
        .collect {
            it?.let {
                onCustomerSelected(CustomerUiState(id = it.toInt(), name = "Jim"))
            }
        }
}
Copy code
Scaffold {
    Column(modifier = Modifier.padding(it)) {
        Text("Customer ID: ${customerId ?: "No customer ID provided"}")
        Button(onClick = {
            navController.previousBackStackEntry?.savedStateHandle?.set(
                "customerId",
                customerId
            )
            navController.popBackStack()
        }) {
            Text("Go back")
        }

    }
}
I just tried out the LaunchedEffect way where I'm passing in the backStackEntry.savedStateHandle and it worked. Wish this could work with the viewModel savedStateHandle but perhaps there's just something going on that I don't understand.
I guess the savedStateHandle on a vm is different from on a backStackEntry for sure, I suppose the vms backStackEntry gets prepopulated with the arguments from the composable though, but then they diverge from then on. Anyone have any ideas on if it's better to just grab the value from the navBackStackEntry or to use the backStackEntry and get the previousBackStackEntrys viewModel? seems like you're scoping a route to only work for a particular path at that point and then scoping your ScreenA viewModel to exist on ScreenB as well.
i
They are indeed separate SavedStateHandles: https://stackoverflow.com/a/76901998/1676363
b
cool, I noticed your example code in the answer uses the getting the viewModel from the previousBackStackEntry approach. I was trying to avoid this as I thought it would be an issue having a ViewModel scoped outside of its main screen, like, why wouldn't you just use a SharedViewModel at this point, since your route has to be scoped to retrieving a specific viewModel from the backStackEntry. i.e. I would need 3 routes for addViewModel, cycleViewModel, scrapViewModel in order to navigate to a generic customer picker, instead of just navigating to /customers, unless this is preferred anyways for deepLink reasons?
ah, just read this. @Calamity - that is indeed why you don't see any documentation or official guides that recommend sending results to a specific instance of a ViewModel that allows external classes to mutate it and precisely why the
NavBackStackEntry.savedStateHandle
API exists - to offer that generalized approach that doesn't tightly couple screen together. so I suppose you recommend just using the navBackStackEntry.savedStateHandle and passing the param in, and then using a LaunchedEffect to trigger a viewModel operation.
690 Views