A lightweight, type-safe way to pass results back between screens when navigating with Compose Multiplatform Navigation.
Compose Navigation has no built-in mechanism for a screen to return data to the screen that launched it. The common workarounds (shared ViewModel, callback lambdas) either add boilerplate or break with the navigation back stack.
Two interfaces that mirror the producer/consumer relationship:
ResultBackNavigator<R>— used by the screen that needs to return a resultResultBackRecipient<R>— used by the screen that receives the result
Results are stored in SavedStateHandle, so they survive process death and configuration changes. Delivery happens on ON_RESUME, which avoids race conditions with the back stack.
ScreenB — returning a result
@Composable
fun ScreenB(resultBackNavigator: ResultBackNavigator<String>) {
var text by remember { mutableStateOf("") }
Button(onClick = { resultBackNavigator.navigateWithResult(text) }) {
Text("Confirm")
}
OutlinedButton(onClick = { resultBackNavigator.navigateBack() }) {
Text("Cancel")
}
}ScreenA — receiving the result
@Composable
fun ScreenA(
navController: NavController,
resultRecipient: ResultBackRecipient<String>
) {
resultRecipient.onResult { result ->
when (result) {
is NavResult.Result -> println("Got: ${result.value}")
is NavResult.Cancelled -> println("Cancelled")
}
}
}Wiring it up in the NavHost
NavHost(navController = navController, startDestination = ScreenA) {
composable<ScreenA> {
ScreenA(
navController = navController,
resultRecipient = it.provideResultRecipient()
)
}
composable<ScreenB> {
ScreenB(resultBackNavigator = it.provideResultBackNavigator(navController))
}
}sealed class NavResult<T> {
class Result<T>(val value: T) : NavResult<T>() // navigateWithResult() was called
object Cancelled : NavResult<Nothing>() // navigateBack() was called
}If the user presses the physical back button, neither case fires — handle that separately with
BackHandlerif needed.
- The result type
Rmust be parcelable on Android (primitive types and strings work out of the box). - Results are keyed by route, so they won't leak between unrelated screen pairs.