From ebc0fdb429fc1f5b60ae5a01cc27d7f8c07b9cdd Mon Sep 17 00:00:00 2001 From: Ben Sagmoe Date: Tue, 18 Aug 2026 21:15:26 +0000 Subject: [PATCH 1/5] Add Navigation 3 ResultEventBus and result passing documentation snippets --- .../decorators/DecoratorSnippets.kt | 24 ++ .../migration/NavigationMigrationAfter.kt | 50 ++- .../migration/NavigationMigrationBefore.kt | 36 +- .../navigation3/results/ResultSnippets.kt | 326 ++++++++++++++++++ gradle/libs.versions.toml | 2 +- 5 files changed, 432 insertions(+), 6 deletions(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt index 4fec8c5d5..2b65d3080 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt @@ -26,6 +26,8 @@ import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay +import androidx.navigation3.runtime.result.rememberResultEventBus +import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator import com.example.compose.snippets.navigation3.savingstate.Home import kotlinx.serialization.Serializable @@ -62,3 +64,25 @@ fun DecoratorsBasic() { ) // [END android_compose_navigation3_decorator_2] } + +@Composable +fun DecoratorsResultEventBus() { + // [START android_compose_navigation3_decorator_result_hoist] + // [START_EXCLUDE] + val backStack = rememberNavBackStack(Home) + val entryProvider = entryProvider { + entry { Text("Welcome to Nav3") } + } + // [END_EXCLUDE] + val resultEventBus = rememberResultEventBus() + + NavDisplay( + backStack = backStack, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) + ), + entryProvider = entryProvider + ) + // [END android_compose_navigation3_decorator_result_hoist] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt index 3a52756c3..d1d33d30c 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt @@ -18,15 +18,19 @@ package com.example.compose.snippets.navigation3.migration import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import kotlinx.coroutines.flow.StateFlow -import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.result.LocalResultEventBus +import androidx.navigation3.runtime.result.ResultEffect import androidx.navigation3.scene.DialogSceneStrategy import androidx.navigation3.ui.NavDisplay +import kotlinx.coroutines.flow.StateFlow import kotlinx.serialization.Serializable // Dummy screens for compilation @@ -141,3 +145,45 @@ private object SnippetLifecycleAfter { // [END android_compose_navigation3_lifecycle_after] } } + +private object SnippetResultAfter { + @Serializable data object ContactPickerRoute : NavKey + data class Contact(val name: String = "") + class ComposeMessageViewModel : ViewModel() { + val recipient: Contact? = null + fun onRecipientSelected(contact: Contact) {} + } + + @Composable private fun ContactPickerScreen(onContactSelected: (Contact) -> Unit) {} + @Composable private fun ComposeMessageContent(recipient: Contact?) {} + + // [START android_compose_navigation3_result_after] + // [START_EXCLUDE] + fun EntryProviderScope.entryProviderResult(navigator: Navigator) { + // [END_EXCLUDE] + // Sender destination (in entryProvider): + entry { + val resultBus = LocalResultEventBus.current + + ContactPickerScreen( + onContactSelected = { contact -> + resultBus.sendResult(result = contact) + navigator.goBack() + } + ) + } + // [START_EXCLUDE] + } + // [END_EXCLUDE] + + // Receiver destination: + @Composable + fun ComposeMessageScreen(viewModel: ComposeMessageViewModel = viewModel()) { + ResultEffect { contact -> + viewModel.onRecipientSelected(contact) + } + + ComposeMessageContent(recipient = viewModel.recipient) + } + // [END android_compose_navigation3_result_after] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationBefore.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationBefore.kt index 298fd72f0..946a79e57 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationBefore.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationBefore.kt @@ -18,22 +18,24 @@ package com.example.compose.snippets.navigation3.migration import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController -import kotlinx.coroutines.flow.StateFlow -import androidx.navigation.NavHostController import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.dialog import androidx.navigation.compose.navigation import androidx.navigation.toRoute -import kotlinx.serialization.Serializable import kotlin.reflect.KClass +import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.Serializable // Dummy screens for compilation @Composable private fun ScreenA(title: String) {} @@ -124,3 +126,31 @@ private object SnippetLifecycleBefore { // [END android_compose_navigation3_lifecycle_before] } } + +private object SnippetResultBefore { + data class Contact(val name: String = "") + class ComposeMessageViewModel : ViewModel() { + fun onRecipientSelected(contact: Contact) {} + } + + @Composable + fun ResultBeforeSnippet( + navController: NavController, + contact: Contact, + viewModel: ComposeMessageViewModel + ) { + // [START android_compose_navigation3_result_before] + // Sender destination: + navController.previousBackStackEntry?.savedStateHandle?.set("contact_key", contact) + navController.popBackStack() + + // Receiver destination: + val lifecycleOwner = LocalLifecycleOwner.current + navController.currentBackStackEntry?.savedStateHandle + ?.getLiveData("contact_key") + ?.observe(lifecycleOwner) { contact -> + viewModel.onRecipientSelected(contact) + } + // [END android_compose_navigation3_result_before] + } +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt new file mode 100644 index 000000000..813dff3b8 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt @@ -0,0 +1,326 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.navigation3.results + +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.runtime.result.LocalResultEventBus +import androidx.navigation3.runtime.result.ResultEffect +import androidx.navigation3.runtime.result.rememberResultEventBus +import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator +import androidx.navigation3.ui.NavDisplay +import kotlinx.serialization.Serializable + +// Domain models & Routes +@Serializable +private data object HomeScreenRoute : NavKey + +@Serializable +private data object ContactPickerRoute : NavKey + +@Serializable +private data object AddressPickerRoute : NavKey + +@Serializable +data class Contact(val name: String = "", val email: String = "") + +@Serializable +data class Address(val street: String = "", val city: String = "") + +enum class ProductFilter { + All, + Electronics, + Books +} + +@Serializable +data class ConfirmationResult(val confirmed: Boolean = true) + +class ComposeMessageViewModel : ViewModel() { + var recipient: Contact? = null + fun onRecipientSelected(contact: Contact) { + recipient = contact + } +} + +class CheckoutViewModel : ViewModel() { + var pickupAddress: Address? = null + fun onPickupAddressSelected(address: Address) { + pickupAddress = address + } +} + +class NotificationViewModel : ViewModel() { + fun onPermissionConfirmed(confirmation: ConfirmationResult) {} +} + +class Navigator { + fun goBack() {} +} + +@Composable +fun ContactPicker(onSelect: (Contact) -> Unit) {} + +@Composable +fun AddressPicker(onSelect: (Address) -> Unit) {} + +@Composable +fun ComposeMessageContent(recipient: Contact?, onPickContact: () -> Unit) {} + +@Composable +fun CheckoutContent(pickupAddress: Address?, onSelectAddress: () -> Unit) {} + +@Composable +fun ProductListContent(activeFilter: ProductFilter, onOpenFilterPicker: () -> Unit) {} + +@Composable +fun OrderSummaryContent(pickupAddress: Address, onSelectAddress: () -> Unit) {} + +private object BasicResultSnippet { + @Composable + fun ResultNavDisplay() { + // [START android_compose_navigation3_result_basic] + NavDisplay( + /* ... */ + // [START_EXCLUDE] + backStack = rememberNavBackStack(HomeScreenRoute), + entryProvider = entryProvider { + entry { } + }, + // [END_EXCLUDE] + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberResultEventBusNavEntryDecorator() + ) + ) + // [END android_compose_navigation3_result_basic] + } +} + +private object SendResultTypeSnippet { + // [START android_compose_navigation3_result_send_type] + // Pure, decoupled screen composable + @Composable + fun ContactPickerScreen( + onContactSelected: (Contact) -> Unit + ) { + ContactPicker(onSelect = onContactSelected) + } + + // [START_EXCLUDE] + fun EntryProviderScope.contactPickerEntry(navigator: Navigator) { + // [END_EXCLUDE] + // In your entryProvider: + entry { + val resultBus = LocalResultEventBus.current + + ContactPickerScreen( + onContactSelected = { selectedContact -> + // Send result by type + resultBus.sendResult(result = selectedContact) + navigator.goBack() + } + ) + } + // [START_EXCLUDE] + } + // [END_EXCLUDE] + // [END android_compose_navigation3_result_send_type] +} + +private object SendResultKeySnippet { + // [START android_compose_navigation3_result_send_key] + // Pure, decoupled screen composable + @Composable + fun AddressPickerScreen( + onAddressSelected: (Address) -> Unit + ) { + AddressPicker(onSelect = onAddressSelected) + } + + // [START_EXCLUDE] + fun EntryProviderScope.addressPickerEntry(navigator: Navigator) { + // [END_EXCLUDE] + // In your entryProvider: + entry { + val resultBus = LocalResultEventBus.current + + AddressPickerScreen( + onAddressSelected = { selectedAddress -> + // Send result with an explicit key + resultBus.sendResult
( + resultKey = "pickup_address", + result = selectedAddress + ) + navigator.goBack() + } + ) + } + // [START_EXCLUDE] + } + // [END_EXCLUDE] + // [END android_compose_navigation3_result_send_key] +} + +private object ReceiveEffectTypeSnippet { + // [START android_compose_navigation3_result_effect_type] + @Composable + fun ComposeMessageScreen( + onPickContact: () -> Unit, + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + viewModel: ComposeMessageViewModel = viewModel() + ) { + val resultBus = LocalResultEventBus.current + + ResultEffect { contact -> + // Suspending calls are supported directly in the effect body + snackbarHostState.showSnackbar("Selected ${contact.name}") + viewModel.onRecipientSelected(contact) + + // Clear after consumption to prevent re-delivery on back-stack re-entry + resultBus.removeResult() + } + + ComposeMessageContent( + recipient = viewModel.recipient, + onPickContact = onPickContact + ) + } + // [END android_compose_navigation3_result_effect_type] +} + +private object ReceiveEffectKeySnippet { + // [START android_compose_navigation3_result_effect_key] + @Composable + fun CheckoutScreen( + onSelectAddress: () -> Unit, + viewModel: CheckoutViewModel = viewModel() + ) { + // Listen for results associated with a specific key + ResultEffect
(resultKey = "pickup_address") { address -> + viewModel.onPickupAddressSelected(address) + } + + CheckoutContent( + pickupAddress = viewModel.pickupAddress, + onSelectAddress = onSelectAddress + ) + } + // [END android_compose_navigation3_result_effect_key] +} + +private object ReceiveStateTypeSnippet { + // [START android_compose_navigation3_result_state_type] + @Composable + fun FilterableProductListScreen( + initialFilter: ProductFilter = ProductFilter.All, + onOpenFilterPicker: () -> Unit + ) { + val resultBus = LocalResultEventBus.current + + // Observe latest filter result as Compose State, starting with initialFilter + val activeFilter by resultBus.conflateAsState( + defaultValue = initialFilter + ) + + ProductListContent( + activeFilter = activeFilter, + onOpenFilterPicker = onOpenFilterPicker + ) + } + // [END android_compose_navigation3_result_state_type] +} + +private object ReceiveStateKeySnippet { + // [START android_compose_navigation3_result_state_key] + @Composable + fun OrderSummaryScreen( + defaultAddress: Address, + onSelectAddress: () -> Unit + ) { + val resultBus = LocalResultEventBus.current + + // Observe latest result with an explicit key as Compose State + val pickupAddress by resultBus.conflateAsState
( + resultKey = "pickup_address", + defaultValue = defaultAddress + ) + + OrderSummaryContent( + pickupAddress = pickupAddress, + onSelectAddress = onSelectAddress + ) + } + // [END android_compose_navigation3_result_state_key] +} + +private object HoistedBusSnippet { + // [START android_compose_navigation3_result_hoist] + @Composable + fun HoistedResultNavigation() { + // Hoist the ResultEventBus at the top level + val resultEventBus = rememberResultEventBus() + + // Pass the hoisted bus to the decorator + val resultEventBusNavEntryDecorator = + rememberResultEventBusNavEntryDecorator( + resultEventBus = resultEventBus + ) + + NavDisplay( + /* ... */ + // [START_EXCLUDE] + backStack = rememberNavBackStack(HomeScreenRoute), + entryProvider = entryProvider { + entry { } + }, + // [END_EXCLUDE] + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + resultEventBusNavEntryDecorator + ) + ) + } + // [END android_compose_navigation3_result_hoist] +} + +private object ClearResultSnippet { + // [START android_compose_navigation3_result_clear] + @Composable + fun NotificationSettingsScreen( + viewModel: NotificationViewModel = viewModel() + ) { + val resultBus = LocalResultEventBus.current + + ResultEffect(resultKey = "confirm_permission") { confirmation -> + viewModel.onPermissionConfirmed(confirmation) + + // Clear the result after consumption to prevent re-delivery + resultBus.removeResult(resultKey = "confirm_permission") + } + } + // [END android_compose_navigation3_result_clear] +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6ed8751b..b481cf4c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ androidx-lifecycle-compose = "2.11.0" androidx-lifecycle-runtime-compose = "2.11.0" androidx-lifecycle-viewmodel-navigation3 = "2.11.0" androidx-navigation = "2.9.8" -androidx-navigation3 = "1.1.5" +androidx-navigation3 = "1.2.0-alpha07" androidx-paging = "3.5.0" androidx-startup-runtime = "1.2.0" androidx-test = "1.7.0" From 75f93c660f3dfdb95a4c75cb3936608073f2590b Mon Sep 17 00:00:00 2001 From: Ben Sagmoe Date: Wed, 19 Aug 2026 13:57:37 +0000 Subject: [PATCH 2/5] Hide backStack and entryProvider in DecoratorsResultEventBus snippet --- .../decorators/DecoratorSnippets.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt index 2b65d3080..86b257dfe 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt @@ -68,21 +68,19 @@ fun DecoratorsBasic() { @Composable fun DecoratorsResultEventBus() { // [START android_compose_navigation3_decorator_result_hoist] - // [START_EXCLUDE] - val backStack = rememberNavBackStack(Home) - val entryProvider = entryProvider { - entry { Text("Welcome to Nav3") } - } - // [END_EXCLUDE] val resultEventBus = rememberResultEventBus() NavDisplay( - backStack = backStack, + // [START_EXCLUDE] + backStack = rememberNavBackStack(Home), + entryProvider = entryProvider { + entry { Text("Welcome to Nav3") } + }, + // [END_EXCLUDE] entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) - ), - entryProvider = entryProvider + rememberSaveableStateHolderNavEntryDecorator(), + rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) + ) ) // [END android_compose_navigation3_decorator_result_hoist] } From 33b47f5e8289d8bb58e2b8db0706620587953a94 Mon Sep 17 00:00:00 2001 From: Ben Sagmoe Date: Wed, 19 Aug 2026 16:59:12 +0000 Subject: [PATCH 3/5] Update ResultSnippets to align with DevSite docs --- .../navigation3/results/ResultSnippets.kt | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt index 813dff3b8..27f786789 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt @@ -68,9 +68,13 @@ class ComposeMessageViewModel : ViewModel() { class CheckoutViewModel : ViewModel() { var pickupAddress: Address? = null + var destinationAddress: Address? = null fun onPickupAddressSelected(address: Address) { pickupAddress = address } + fun onDestinationAddressSelected(address: Address) { + destinationAddress = address + } } class NotificationViewModel : ViewModel() { @@ -91,13 +95,21 @@ fun AddressPicker(onSelect: (Address) -> Unit) {} fun ComposeMessageContent(recipient: Contact?, onPickContact: () -> Unit) {} @Composable -fun CheckoutContent(pickupAddress: Address?, onSelectAddress: () -> Unit) {} +fun CheckoutContent( + pickupAddress: Address?, + destinationAddress: Address?, + onOpenAddressPicker: () -> Unit +) {} @Composable fun ProductListContent(activeFilter: ProductFilter, onOpenFilterPicker: () -> Unit) {} @Composable -fun OrderSummaryContent(pickupAddress: Address, onSelectAddress: () -> Unit) {} +fun OrderSummaryContent( + pickupAddress: Address?, + destinationAddress: Address?, + onOpenAddressPicker: () -> Unit +) {} private object BasicResultSnippet { @Composable @@ -193,15 +205,10 @@ private object ReceiveEffectTypeSnippet { snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, viewModel: ComposeMessageViewModel = viewModel() ) { - val resultBus = LocalResultEventBus.current - ResultEffect { contact -> // Suspending calls are supported directly in the effect body snackbarHostState.showSnackbar("Selected ${contact.name}") viewModel.onRecipientSelected(contact) - - // Clear after consumption to prevent re-delivery on back-stack re-entry - resultBus.removeResult() } ComposeMessageContent( @@ -216,7 +223,7 @@ private object ReceiveEffectKeySnippet { // [START android_compose_navigation3_result_effect_key] @Composable fun CheckoutScreen( - onSelectAddress: () -> Unit, + onOpenAddressPicker: () -> Unit, viewModel: CheckoutViewModel = viewModel() ) { // Listen for results associated with a specific key @@ -224,9 +231,14 @@ private object ReceiveEffectKeySnippet { viewModel.onPickupAddressSelected(address) } + ResultEffect
(resultKey = "destination_address") { address -> + viewModel.onDestinationAddressSelected(address) + } + CheckoutContent( pickupAddress = viewModel.pickupAddress, - onSelectAddress = onSelectAddress + destinationAddress = viewModel.destinationAddress, + onOpenAddressPicker = onOpenAddressPicker ) } // [END android_compose_navigation3_result_effect_key] @@ -258,20 +270,25 @@ private object ReceiveStateKeySnippet { // [START android_compose_navigation3_result_state_key] @Composable fun OrderSummaryScreen( - defaultAddress: Address, - onSelectAddress: () -> Unit + onOpenAddressPicker: () -> Unit ) { val resultBus = LocalResultEventBus.current // Observe latest result with an explicit key as Compose State - val pickupAddress by resultBus.conflateAsState
( + val pickupAddress by resultBus.conflateAsState( resultKey = "pickup_address", - defaultValue = defaultAddress + defaultValue = null + ) + + val destinationAddress by resultBus.conflateAsState( + resultKey = "destination_address", + defaultValue = null ) OrderSummaryContent( pickupAddress = pickupAddress, - onSelectAddress = onSelectAddress + destinationAddress = destinationAddress, + onOpenAddressPicker = onOpenAddressPicker ) } // [END android_compose_navigation3_result_state_key] From 6d57b33541e4f327a5aba2e1117abeaa4aa99761 Mon Sep 17 00:00:00 2001 From: Ben Sagmoe Date: Thu, 10 Sep 2026 18:46:08 +0000 Subject: [PATCH 4/5] Update Navigation 3 result and migration snippets - Align result snippets and decorators with updated docs - Simplify region tags and minimize exclude blocks --- .../decorators/DecoratorSnippets.kt | 7 +- .../migration/NavigationMigrationAfter.kt | 24 +- .../navigation3/results/ResultSnippets.kt | 309 +++++++++++------- 3 files changed, 199 insertions(+), 141 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt index 86b257dfe..3395a1930 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/decorators/DecoratorSnippets.kt @@ -71,15 +71,16 @@ fun DecoratorsResultEventBus() { val resultEventBus = rememberResultEventBus() NavDisplay( - // [START_EXCLUDE] + /* ... */ + // [START_EXCLUDE silent] backStack = rememberNavBackStack(Home), entryProvider = entryProvider { entry { Text("Welcome to Nav3") } }, // [END_EXCLUDE] entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) + rememberSaveableStateHolderNavEntryDecorator(), + rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) ) ) // [END android_compose_navigation3_decorator_result_hoist] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt index d1d33d30c..0e0bdec67 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/migration/NavigationMigrationAfter.kt @@ -157,10 +157,8 @@ private object SnippetResultAfter { @Composable private fun ContactPickerScreen(onContactSelected: (Contact) -> Unit) {} @Composable private fun ComposeMessageContent(recipient: Contact?) {} - // [START android_compose_navigation3_result_after] - // [START_EXCLUDE] fun EntryProviderScope.entryProviderResult(navigator: Navigator) { - // [END_EXCLUDE] + // [START android_compose_navigation3_result_after] // Sender destination (in entryProvider): entry { val resultBus = LocalResultEventBus.current @@ -172,18 +170,16 @@ private object SnippetResultAfter { } ) } - // [START_EXCLUDE] - } - // [END_EXCLUDE] - // Receiver destination: - @Composable - fun ComposeMessageScreen(viewModel: ComposeMessageViewModel = viewModel()) { - ResultEffect { contact -> - viewModel.onRecipientSelected(contact) - } + // Receiver destination: + @Composable + fun ComposeMessageScreen(viewModel: ComposeMessageViewModel = viewModel()) { + ResultEffect { contact -> + viewModel.onRecipientSelected(contact) + } - ComposeMessageContent(recipient = viewModel.recipient) + ComposeMessageContent(recipient = viewModel.recipient) + } + // [END android_compose_navigation3_result_after] } - // [END android_compose_navigation3_result_after] } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt index 27f786789..0749a0d26 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt @@ -16,10 +16,12 @@ package com.example.compose.snippets.navigation3.results +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.EntryProviderScope @@ -66,7 +68,7 @@ class ComposeMessageViewModel : ViewModel() { } } -class CheckoutViewModel : ViewModel() { +class RideSummaryViewModel : ViewModel() { var pickupAddress: Address? = null var destinationAddress: Address? = null fun onPickupAddressSelected(address: Address) { @@ -91,24 +93,36 @@ fun ContactPicker(onSelect: (Contact) -> Unit) {} @Composable fun AddressPicker(onSelect: (Address) -> Unit) {} +@Composable +fun AddressPickerScreen(onAddressSelected: (Address) -> Unit) { + AddressPicker(onSelect = onAddressSelected) +} + +@Composable +fun ContactPickerScreen(onContactSelected: (Contact) -> Unit) { + ContactPicker(onSelect = onContactSelected) +} + @Composable fun ComposeMessageContent(recipient: Contact?, onPickContact: () -> Unit) {} @Composable -fun CheckoutContent( +fun RideSummaryContent( pickupAddress: Address?, destinationAddress: Address?, - onOpenAddressPicker: () -> Unit + onPickPickup: () -> Unit, + onPickDestination: () -> Unit ) {} @Composable fun ProductListContent(activeFilter: ProductFilter, onOpenFilterPicker: () -> Unit) {} @Composable -fun OrderSummaryContent( - pickupAddress: Address?, - destinationAddress: Address?, - onOpenAddressPicker: () -> Unit +fun ThemePreviewContent( + primaryColor: Color, + accentColor: Color, + onPickPrimary: () -> Unit, + onPickAccent: () -> Unit ) {} private object BasicResultSnippet { @@ -117,7 +131,7 @@ private object BasicResultSnippet { // [START android_compose_navigation3_result_basic] NavDisplay( /* ... */ - // [START_EXCLUDE] + // [START_EXCLUDE silent] backStack = rememberNavBackStack(HomeScreenRoute), entryProvider = entryProvider { entry { } @@ -132,73 +146,102 @@ private object BasicResultSnippet { } } -private object SendResultTypeSnippet { - // [START android_compose_navigation3_result_send_type] - // Pure, decoupled screen composable - @Composable - fun ContactPickerScreen( - onContactSelected: (Contact) -> Unit - ) { - ContactPicker(onSelect = onContactSelected) +private object SendResultKeySnippet { + /* + // [START android_compose_navigation3_result_send_key] + import androidx.compose.runtime.Composable + import androidx.navigation3.runtime.result.LocalResultEventBus + // [START_EXCLUDE silent] + */ + fun EntryProviderScope.addressPickerEntry(navigator: Navigator) { + // [END_EXCLUDE] + + entry { + val resultBus = LocalResultEventBus.current + + AddressPickerScreen( + onAddressSelected = { selectedAddress: Address -> + resultBus.sendResult( + resultKey = "pickup_address", + result = selectedAddress + ) + navigator.goBack() + } + ) + } + // [END android_compose_navigation3_result_send_key] } +} - // [START_EXCLUDE] +private object SendResultTypeSnippet { + /* + // [START android_compose_navigation3_result_send_type] + import androidx.compose.runtime.Composable + import androidx.navigation3.runtime.result.LocalResultEventBus + // [START_EXCLUDE silent] + */ fun EntryProviderScope.contactPickerEntry(navigator: Navigator) { // [END_EXCLUDE] - // In your entryProvider: - entry { - val resultBus = LocalResultEventBus.current - - ContactPickerScreen( - onContactSelected = { selectedContact -> - // Send result by type - resultBus.sendResult(result = selectedContact) - navigator.goBack() - } - ) - } - // [START_EXCLUDE] + + entry { + val resultBus = LocalResultEventBus.current + + ContactPickerScreen( + onContactSelected = { selectedContact: Contact -> + resultBus.sendResult(result = selectedContact) + navigator.goBack() + } + ) } - // [END_EXCLUDE] // [END android_compose_navigation3_result_send_type] + } } -private object SendResultKeySnippet { - // [START android_compose_navigation3_result_send_key] - // Pure, decoupled screen composable +private object ReceiveEffectKeySnippet { + /* + // [START android_compose_navigation3_result_effect_key] + import androidx.compose.runtime.Composable + import androidx.lifecycle.viewmodel.compose.viewModel + import androidx.navigation3.runtime.result.ResultEffect + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + @Composable - fun AddressPickerScreen( - onAddressSelected: (Address) -> Unit + fun RideSummaryScreen( + onOpenAddressPicker: (key: String) -> Unit, + viewModel: RideSummaryViewModel = viewModel() ) { - AddressPicker(onSelect = onAddressSelected) - } + ResultEffect
(resultKey = "pickup_address") { address -> + viewModel.onPickupAddressSelected(address) + } - // [START_EXCLUDE] - fun EntryProviderScope.addressPickerEntry(navigator: Navigator) { - // [END_EXCLUDE] - // In your entryProvider: - entry { - val resultBus = LocalResultEventBus.current - - AddressPickerScreen( - onAddressSelected = { selectedAddress -> - // Send result with an explicit key - resultBus.sendResult
( - resultKey = "pickup_address", - result = selectedAddress - ) - navigator.goBack() - } - ) + ResultEffect
(resultKey = "destination_address") { address -> + viewModel.onDestinationAddressSelected(address) } - // [START_EXCLUDE] + + RideSummaryContent( + pickupAddress = viewModel.pickupAddress, + destinationAddress = viewModel.destinationAddress, + onPickPickup = { onOpenAddressPicker("pickup_address") }, + onPickDestination = { onOpenAddressPicker("destination_address") } + ) } - // [END_EXCLUDE] - // [END android_compose_navigation3_result_send_key] + // [END android_compose_navigation3_result_effect_key] } private object ReceiveEffectTypeSnippet { + /* // [START android_compose_navigation3_result_effect_type] + import androidx.compose.material3.SnackbarHostState + import androidx.compose.runtime.Composable + import androidx.compose.runtime.remember + import androidx.lifecycle.viewmodel.compose.viewModel + import androidx.navigation3.runtime.result.ResultEffect + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + @Composable fun ComposeMessageScreen( onPickContact: () -> Unit, @@ -219,33 +262,54 @@ private object ReceiveEffectTypeSnippet { // [END android_compose_navigation3_result_effect_type] } -private object ReceiveEffectKeySnippet { - // [START android_compose_navigation3_result_effect_key] +private object ReceiveStateKeySnippet { + /* + // [START android_compose_navigation3_result_state_key] + import androidx.compose.material3.MaterialTheme + import androidx.compose.runtime.Composable + import androidx.compose.runtime.getValue + import androidx.compose.ui.graphics.Color + import androidx.navigation3.runtime.result.LocalResultEventBus + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + @Composable - fun CheckoutScreen( - onOpenAddressPicker: () -> Unit, - viewModel: CheckoutViewModel = viewModel() + fun ThemePreviewScreen( + onOpenColorPicker: (key: String) -> Unit ) { - // Listen for results associated with a specific key - ResultEffect
(resultKey = "pickup_address") { address -> - viewModel.onPickupAddressSelected(address) - } + val resultBus = LocalResultEventBus.current - ResultEffect
(resultKey = "destination_address") { address -> - viewModel.onDestinationAddressSelected(address) - } + val primaryColor by resultBus.conflateAsState( + resultKey = "primary_color", + defaultValue = MaterialTheme.colorScheme.primary + ) - CheckoutContent( - pickupAddress = viewModel.pickupAddress, - destinationAddress = viewModel.destinationAddress, - onOpenAddressPicker = onOpenAddressPicker + val accentColor by resultBus.conflateAsState( + resultKey = "accent_color", + defaultValue = MaterialTheme.colorScheme.tertiary + ) + + ThemePreviewContent( + primaryColor = primaryColor, + accentColor = accentColor, + onPickPrimary = { onOpenColorPicker("primary_color") }, + onPickAccent = { onOpenColorPicker("accent_color") } ) } - // [END android_compose_navigation3_result_effect_key] + // [END android_compose_navigation3_result_state_key] } private object ReceiveStateTypeSnippet { + /* // [START android_compose_navigation3_result_state_type] + import androidx.compose.runtime.Composable + import androidx.compose.runtime.getValue + import androidx.navigation3.runtime.result.LocalResultEventBus + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + @Composable fun FilterableProductListScreen( initialFilter: ProductFilter = ProductFilter.All, @@ -266,66 +330,63 @@ private object ReceiveStateTypeSnippet { // [END android_compose_navigation3_result_state_type] } -private object ReceiveStateKeySnippet { - // [START android_compose_navigation3_result_state_key] - @Composable - fun OrderSummaryScreen( - onOpenAddressPicker: () -> Unit - ) { - val resultBus = LocalResultEventBus.current - - // Observe latest result with an explicit key as Compose State - val pickupAddress by resultBus.conflateAsState( - resultKey = "pickup_address", - defaultValue = null - ) - - val destinationAddress by resultBus.conflateAsState( - resultKey = "destination_address", - defaultValue = null - ) - - OrderSummaryContent( - pickupAddress = pickupAddress, - destinationAddress = destinationAddress, - onOpenAddressPicker = onOpenAddressPicker - ) - } - // [END android_compose_navigation3_result_state_key] -} - private object HoistedBusSnippet { + /* // [START android_compose_navigation3_result_hoist] + import androidx.compose.runtime.Composable + import androidx.navigation3.runtime.result.rememberResultEventBus + import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator + import androidx.navigation3.ui.NavDisplay + // [START_EXCLUDE silent] + */ @Composable fun HoistedResultNavigation() { - // Hoist the ResultEventBus at the top level - val resultEventBus = rememberResultEventBus() - - // Pass the hoisted bus to the decorator - val resultEventBusNavEntryDecorator = - rememberResultEventBusNavEntryDecorator( - resultEventBus = resultEventBus - ) + // [END_EXCLUDE] - NavDisplay( - /* ... */ - // [START_EXCLUDE] - backStack = rememberNavBackStack(HomeScreenRoute), - entryProvider = entryProvider { - entry { } - }, - // [END_EXCLUDE] - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - resultEventBusNavEntryDecorator - ) + // Hoist the ResultEventBus at the top level + val resultEventBus = rememberResultEventBus() + + // Pass the hoisted bus to the decorator + val resultEventBusNavEntryDecorator = + // [START_EXCLUDE silent] + rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) + /* + // [END_EXCLUDE] + rememberResultEventBusNavEntryDecorator( + resultEventBus = resultEventBus ) - } + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + + NavDisplay( + /* ... */ + // [START_EXCLUDE silent] + backStack = rememberNavBackStack(HomeScreenRoute), + entryProvider = entryProvider { + entry { } + }, + // [END_EXCLUDE] + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + resultEventBusNavEntryDecorator + ) + ) // [END android_compose_navigation3_result_hoist] + } } private object ClearResultSnippet { + /* // [START android_compose_navigation3_result_clear] + import androidx.compose.runtime.Composable + import androidx.lifecycle.viewmodel.compose.viewModel + import androidx.navigation3.runtime.result.LocalResultEventBus + import androidx.navigation3.runtime.result.ResultEffect + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + @Composable fun NotificationSettingsScreen( viewModel: NotificationViewModel = viewModel() From 49943284a387abdea1bd85890c3d173f868fb4cc Mon Sep 17 00:00:00 2001 From: Ben Sagmoe Date: Thu, 10 Sep 2026 19:00:10 +0000 Subject: [PATCH 5/5] Fix hoisted decorator snippet --- .../snippets/navigation3/results/ResultSnippets.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt index 0749a0d26..4f7be6889 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/results/ResultSnippets.kt @@ -348,16 +348,9 @@ private object HoistedBusSnippet { // Pass the hoisted bus to the decorator val resultEventBusNavEntryDecorator = - // [START_EXCLUDE silent] - rememberResultEventBusNavEntryDecorator(resultEventBus = resultEventBus) - /* - // [END_EXCLUDE] - rememberResultEventBusNavEntryDecorator( + rememberResultEventBusNavEntryDecorator( resultEventBus = resultEventBus ) - // [START_EXCLUDE silent] - */ - // [END_EXCLUDE] NavDisplay( /* ... */