Jetpack Compose Sending Result Back with SavedStateHandle does not work with SavedStateHandle injected in ViewModel

1.8k Views Asked by At

Sending Result Back with SavedStateHandle does not work with SavedStateHandle injected in ViewModel.

Getting result using navController.currentBackStackEntry?.savedStateHandle? it works!

fun CreatePostScreen(
    navController: NavController,
    coroutineScope: CoroutineScope,
    snackbarState: SnackbarHostState,
    viewModel: CreatePostViewModel = hiltViewModel(),
) {

    LaunchedEffect(key1 = Unit) {

        navController.currentBackStackEntry?.savedStateHandle?.getStateFlow(
            "result", ""
        )?.collect { result ->
            Timber.d("Result -> $result")
        }
    }
}

Using saveStateHandle injected using Hilt in ViewModel doesn't get the result!

@HiltViewModel
class CreatePostViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
    
    init {

        viewModelScope.launch {
            savedStateHandle.getStateFlow("result", "").collect {
                Timber.d("Result -> $it")
            }
        }
    }
}

That's how I'm sending the result back to the previous screen!

navController.previousBackStackEntry?.savedStateHandle?.set("result", "this is result")
1

There are 1 best solutions below

5
ianhanniballake On BEST ANSWER

The important thing to realize is that every ViewModel instance gets its own SavedStateHandle - if you accessed two separate ViewModel classes on the same screen, they would each have their own SavedStateHandle.

So when you call navController.currentBackStackEntry?.savedStateHandle, you aren't actually getting the SavedStateHandle associated with your CreatePostViewModel - if you look at the NavBackStackEntry source code, you'll note that the SavedStateHandle it is returning is for a private ViewModel subclass that is completely independent of any other ViewModels you create.

Therefore if you want to send a result back specifically to your own custom ViewModel (like your CreatePostViewModel), you need to specifically ask for exactly that ViewModel in your other screen:

// Assumes `it` is the current NavBackStackEntry that was passed to you
// from the composable() lambda
val previousBackStackEntry = remember(it) {
  navController.previousBackStackEntry!!
}
val previousViewModel = hiltViewModel<CreatePostViewModel>(previouslyBackStackEntry)
previousViewModel.savedStateHandle?.set("result", "this is result")

Note that with this approach, you need to specifically ask for the ViewModel by its exact class name - that's because the class name is the default key that is passed to the viewModel() method and similarly for hiltViewModel().