從 Navigation 3 1.2.0 開始,您可以使用 ResultEventBus API 從目的地傳回結果。
ResultEventBus 提供兩種通訊模型:
- 以事件為準的結果:針對暫時性的一次性事件 (例如顯示確認 Snackbar 或觸發副作用),請使用
ResultEffect。 - 以狀態為準的結果:觀察 Compose
State的最新結果,方法是使用conflateAsState。
設定結果事件匯流排
如要讓可組合目的地使用 ResultEventBus,請將 rememberResultEventBusNavEntryDecorator 新增至傳遞至 NavDisplay 的裝飾器清單。這會為每個目的地內容提供 LocalResultEventBus 組合區域。
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
結果鍵
ResultEventBus 會使用索引鍵識別並傳送每個結果。傳送者和接收者會使用相同金鑰比對結果。
您可以透過下列兩種方式指定結果鍵:
- 明確鍵:您可以指定明確鍵 (例如
resultKey = "pickup_address")。傳回常見型別 (例如String、Boolean或原始型別),或多個目的地傳回相同資料型別的不同執行個體時,請使用明確鍵。 - 類型衍生鍵:如果未指定明確的鍵,
ResultEventBus會自動使用結果類型KClass的toString表示法 (例如Contact::class.toString()) 產生鍵。請針對不同的網域專屬資料類型使用類型衍生鍵。
從目的地傳回結果
為確保畫面可組合項可重複使用及測試,請勿在畫面 UI 內直接存取 LocalResultEventBus。請改為從畫面公開回呼 Lambda。在 entryProvider 中,使用 LocalResultEventBus.current 傳送結果並返回上一個畫面,藉此處理回呼。
你可以使用明確的結果鍵傳送結果:
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.LocalResultEventBus entry<AddressPickerRoute> { val resultBus = LocalResultEventBus.current AddressPickerScreen( onAddressSelected = { selectedAddress: Address -> resultBus.sendResult( resultKey = "pickup_address", result = selectedAddress ) navigator.goBack() } ) }
您也可以使用衍生自型別的鍵傳送結果:
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.LocalResultEventBus entry<ContactPickerRoute> { val resultBus = LocalResultEventBus.current ContactPickerScreen( onContactSelected = { selectedContact: Contact -> resultBus.sendResult(result = selectedContact) navigator.goBack() } ) }
接收結果
目的地可以使用事件型效果或狀態型可觀測項,取用結果。
| API | 行為 | 建議用途 |
|---|---|---|
ResultEffect |
已加入佇列:依序處理針對鍵發出的所有結果。 | 單次事件和副作用 (例如顯示 Snackbar 或轉送至 ViewModel)。 |
conflateAsState |
合併:捨棄中繼結果,只保留最新結果做為 Compose State。 |
輕量型 UI 狀態修飾符 (例如有效篩選器標記或選取項目覆寫)。 |
使用 ResultEffect 處理一次性事件
處理一次性事件時,請使用 ResultEffect,例如觸發 Analytics、顯示 Snackbar,或將結果轉送至 ViewModel。
ResultEffect 會維護傳入結果的佇列。如果針對特定鍵傳送多個結果,ResultEffect 會依傳送順序處理每個結果。此外,ResultEffect 會在協同程式範圍中執行,因此您可以在效果主體內直接呼叫暫停函式。
您可以監聽與明確結果鍵相關聯的結果:
import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.ResultEffect @Composable fun RideSummaryScreen( onOpenAddressPicker: (key: String) -> Unit, viewModel: RideSummaryViewModel = viewModel() ) { ResultEffect<Address>(resultKey = "pickup_address") { address -> viewModel.onPickupAddressSelected(address) } ResultEffect<Address>(resultKey = "destination_address") { address -> viewModel.onDestinationAddressSelected(address) } RideSummaryContent( pickupAddress = viewModel.pickupAddress, destinationAddress = viewModel.destinationAddress, onPickPickup = { onOpenAddressPicker("pickup_address") }, onPickDestination = { onOpenAddressPicker("destination_address") } ) }
您也可以使用型別衍生鍵來監聽結果:
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 @Composable fun ComposeMessageScreen( onPickContact: () -> Unit, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, viewModel: ComposeMessageViewModel = viewModel() ) { ResultEffect<Contact> { contact -> // Suspending calls are supported directly in the effect body snackbarHostState.showSnackbar("Selected ${contact.name}") viewModel.onRecipientSelected(contact) } ComposeMessageContent( recipient = viewModel.recipient, onPickContact = onPickContact ) }
在目的地之間導覽時,ResultEffect 會透過下列生命週期序列執行:
- 傳送者發出:傳送者目的地會使用
resultBus.sendResult(resultKey = "pickup_address", address)傳送結果,並彈出返回堆疊。 - 接收器進入組合:接收器目的地會成為有效畫面,且
ResultEffect開始監聽結果。 - 接收器處理結果:
ResultEffect會接收並執行針對該鍵傳送的每個結果的效果主體,並依傳送順序處理所有發出的項目。 - 接收器離開組合:當接收器目的地從返回堆疊中彈出時,
ResultEffect會離開組合,並停止監聽結果。
使用 conflateAsState 以狀態形式觀察最新結果
如果您只需要最新的結果值,直接修改或篩選本機 UI 狀態,並希望 Compose 在結果更新時自動重組,請在 ResultEventBus 上呼叫 conflateAsState。
您可以觀察與明確結果鍵相關聯的結果:
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 @Composable fun ThemePreviewScreen( onOpenColorPicker: (key: String) -> Unit ) { val resultBus = LocalResultEventBus.current val primaryColor by resultBus.conflateAsState<Color>( resultKey = "primary_color", defaultValue = MaterialTheme.colorScheme.primary ) val accentColor by resultBus.conflateAsState<Color>( resultKey = "accent_color", defaultValue = MaterialTheme.colorScheme.tertiary ) ThemePreviewContent( primaryColor = primaryColor, accentColor = accentColor, onPickPrimary = { onOpenColorPicker("primary_color") }, onPickAccent = { onOpenColorPicker("accent_color") } ) }
您也可以使用衍生自型別的鍵觀察結果:
import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.navigation3.runtime.result.LocalResultEventBus @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<ProductFilter>( defaultValue = initialFilter ) ProductListContent( activeFilter = activeFilter, onOpenFilterPicker = onOpenFilterPicker ) }
起重機 ResultEventBus
根據預設,rememberResultEventBusNavEntryDecorator 會使用 rememberResultEventBus 在內部建立並記住自己的 ResultEventBus。
您可以在需要時明確建立及提升 ResultEventBus:
- 將
ResultEventBus例項直接傳遞至非可組合元件或依附元件注入圖表。 - 從頂層應用程式架構 (例如應用程式列或導覽匣) 傳送或觀察目的地階層外部的結果。
如要升起 ResultEventBus,請使用 rememberResultEventBus 建立該項目,並傳遞至 rememberResultEventBusNavEntryDecorator(resultEventBus):
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.rememberResultEventBus import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator import androidx.navigation3.ui.NavDisplay // Hoist the ResultEventBus at the top level val resultEventBus = rememberResultEventBus() // Pass the hoisted bus to the decorator val resultEventBusNavEntryDecorator = rememberResultEventBusNavEntryDecorator<NavKey>( resultEventBus = resultEventBus ) NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), resultEventBusNavEntryDecorator ) )
管理及清除結果
目的地使用一次性結果後,請使用 removeResult 從事件匯流排清除結果。這可防止目的地重新進入組合時,匯流排將過去的事件重新傳送給新的觀察者:
import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.LocalResultEventBus import androidx.navigation3.runtime.result.ResultEffect @Composable fun NotificationSettingsScreen( viewModel: NotificationViewModel = viewModel() ) { val resultBus = LocalResultEventBus.current ResultEffect<ConfirmationResult>(resultKey = "confirm_permission") { confirmation -> viewModel.onPermissionConfirmed(confirmation) // Clear the result after consumption to prevent re-delivery resultBus.removeResult(resultKey = "confirm_permission") } }
您可以透過明確鍵 (resultBus.removeResult(resultKey)) 或類型衍生鍵 (resultBus.removeResult<T>()) 清除結果。如要瞭解鍵比對的詳細資料,請參閱「結果鍵」。
食譜
如需完整的可執行程式碼範例,瞭解如何使用不同的結果傳遞策略,請參閱下列食譜: