This is how I send a result from an edit screen ba...
# compose
f
This is how I send a result from an edit screen back to the parent screen on the backstack. But I would like to receive this event only once (like it is the case for the old fragment result API). Is there a way to do that?
Copy code
composable(TodoScreen.TodoList.name) { navBackStackEntry ->
            TodoListScreen(
                [...]
                addEditResult = navBackStackEntry.savedStateHandle.get<Int>(KEY_ADD_EDIT_RESULT)
            )
        }
As far as I see, I will always receive this result on every recomposition (like when the screen is rotated) causing to show feedback snackbars again.
i
Quoting the docs (https://developer.android.com/guide/navigation/navigation-programmatic#returning_a_result):
If you’d only like to handle a result only once, you must call 
remove()
 on the 
SavedStateHandle
 to clear the result.
f
@Ian Lake Thank you. I tried it earlier and didn't work. But I think I know why. Because the backstack entry and the ViewModel have different SavedStateHandles.
I was trying to clear the value from the ViewModel but I guess I have to clear it directly in the Composable.
i
If you are using
navBackStackEntry.savedStateHandle
, you need to call
remove()
on
navBackStackEntry.savedStateHandle
, yes
f
alright, thank you very much!
@Ian Lake Like this right?
Copy code
NavHost(
        navController = navController,
        startDestination = TodoScreen.TodoList.name,
        modifier = modifier
    ) {
        composable(TodoScreen.TodoList.name) { navBackStackEntry ->
            val addEditResult = navBackStackEntry.savedStateHandle.get<Int>(KEY_ADD_EDIT_RESULT)
            navBackStackEntry.savedStateHandle.remove<Int>(KEY_ADD_EDIT_RESULT)
            TodoListScreen(
                onTodoEditClicked = { todo ->
                    navController.navigate("${TodoScreen.AddEditTodo.name}?todoId=${todo.id}")
                },
                onFabAddNewTodoClicked = {
                    navController.navigate(TodoScreen.AddEditTodo.name)
                },
                addEditResult = addEditResult
            )
        }
i
I'm always a little worried about calling imperative methods (like
remove
) as part of composition - generally you'd want to handle events through one of the
Effects
APIs
Like in this case, on recomposition
addEditResult
will be null again (because you removed it), thus caling your
TodoListScreen
to be passed null again. That seems to imply that you only care about a single composition happening with a valid result, which feels weird?
You'll definitely be recomposed multiple times as enter animations occur
How are you handling recomposition, events, and state in this case?
f
in the Composable itself I handle the result in a `DisposableEffect`:
Copy code
DisposableEffect(addEditResult) {
        if (addEditResult != null) {
            viewModel.onAddEditResult(addEditResult)
        }
        onDispose {}
    }
where else could I remove this value from the SavedStateHandle?
i
Well, generally you'd remove it after you finish processing it, so after your
onAddEditResult
returns
f
but for this I need a reference to the backstackEntry inside the composable
can I just call rememberNavController there?
i
Which would involve either passing the
SavedStateHandle
down (so you could call
remove
directly) or passing in a lambda parameter that would call
remove
when it fires
Copy code
TodoListScreen(
  onTodoEditClicked = { todo ->
    navController.navigate("${TodoScreen.AddEditTodo.name}?todoId=${todo.id}")
  },
  onFabAddNewTodoClicked = {
    navController.navigate(TodoScreen.AddEditTodo.name)
  },
  addEditResult = navBackStackEntry.savedStateHandle.get<Int>(KEY_ADD_EDIT_RESULT)
  onAddEditResultProcessed = {
    navBackStackEntry.savedStateHandle.remove(KEY_ADD_EDIT_RESULT)
  }
)
f
ok, thank you
and on the receiving side I do this?
Copy code
@Composable
fun TodoListScreen(
    [...]
    addEditResult: Int?,
    onAddEditResultProcessed: () -> Unit
) {
    [...]

    DisposableEffect(addEditResult) {
        if (addEditResult != null) {
            viewModel.onAddEditResult(addEditResult)
            onAddEditResultProcessed()
        }
        onDispose {}
    }

    [...]
}
i
Yeah
f
thank you 👌