Navigating back with a result
You can send results back in a type-safe and simple way. Let's see how:
- Add a
ResultBackNavigator
parameter to the screen that will send results back:
@Destination(style = AppDialog::class)
@Composable
fun GoToProfileConfirmation(
resultNavigator: ResultBackNavigator<Boolean>
) { //...
ResultBackNavigator
has a type argument that is corresponding to the type of result you want to send back.- The above example is a Dialog. Modal destinations (dialogs, bottom sheet) are good examples of screens that want to send a result to previous screens.
- When the screen is done, call
navigateBack
function passing in the result:
resultNavigator.navigateBack(result = true)
This will finish the current screen (same as calling navigateUp
on normal navigator) and pass true
to the previous screen.
- Get a result back from such a Destination:
@Composable
fun GreetingScreen(
navigator: DestinationsNavigator,
resultRecipient: ResultRecipient<GoToProfileConfirmationDestination, Boolean>
) {
resultRecipient.onNavResult { result ->
when (result) {
is NavResult.Canceled -> {
// `GoToProfileConfirmationDestination` was shown but it was canceled
// and no value was set (example: dialog/bottom sheet dismissed)
}
is NavResult.Value -> {
println("result received from GoToProfileConfirmationDestination = ${result.value}")
// Do whatever with the result received!
// Think of it like a button click, usually you want to call
// a view model method here or navigate somewhere
}
}
}
// Navigate normally to the other screen, example:
Button(
onClick = {
navigator.navigate(GoToProfileConfirmationDestination)
}
) { //...
}
Notice the type arguments of ResultRecipient
. The first is the Destination
that is going to send results to the recipient and the second is the type of result the recipient is expecting.
The onNavResult
listener will be called every time the GoToProfileConfirmation
(in this case) calls navigateBack
on its ResultBackNavigator
and receives the result sent through that call.
If GoToProfileConfirmation
screen is shown and then gets popped out of the back stack and no result is set (examples: it calls navigateBack
with no result set; it is a dialog and it gets dismissed; etc), then the onNavResult
gets called with NavResult.Canceled
so that you can react to this.
Safety enforced at compile time:
- Screens can have at most one
ResultBackNavigator
argument. - Screens can have at most one
ResultRecipient
perDestination
type. This means you can have multiple recipients only if they are related to different Destinations. - Result type must be one of String, Boolean, Float, Int, Long, Serializable, or Parcelable. They can be nullable but in the case of Serializables and Parcelables, they cannot contain type arguments.
- For every
ResultRecipient
of a result typeR
, the corresponding destination must also have aResultBackNavigator
of that sameR
type.
Multi module result back case
In multi module apps, you may find a scenario where there is no dependency between result "sender" and result "recipient". In those scenarios, you need to use OpenResultRecipient<Boolean>
(example) instead of ResultRecipient<YourConfirmationDestination, Boolean>
.
After this though, there is no way for the library to know how to pass these recipient to your destination, so you need to pass it manually by calling the destinations asking for this kind of OpenResultRecipient
:
DestinationsNavHost(
//...
) {
composable(YourRecipientScreenDestination) {
YourRecipientScreen(
//...
resultRecipient = resultRecipient<YourConfirmationDestination, Boolean>()
)
}
}
As you can see, the place that calls DestinationsNavHost is the one that decides where the result comes from. This way we can use this for multi module apps where there is no dependency between recipient and result destinations.
There is no check at compile time, and it's a bit of manual setup to use this feature. So always prefer to use the type-safe approach unless you can't - usually only when the destinations at play belong to different modules.