自 Navigation 3 1.2.0 起,您可以使用 ResultEventBus API 從目的地傳回結果。
ResultEventBus 提供兩種通訊模式:
- 事件型結果:使用
ResultEffect處理暫時性的一次性事件 (例如顯示確認 Snackbar 或觸發副作用)。 - 以狀態為基礎的結果:使用
conflateAsState觀察 ComposeState的最新結果。
設定結果事件匯流排
如要讓可組合目的地使用 ResultEventBus,請將 rememberResultEventBusNavEntryDecorator 新增至傳遞至 NavDisplay 的裝飾器清單。這會為每個目的地的內容提供 LocalResultEventBus 組合本機。
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
結果鍵
ResultEventBus 會使用索引鍵識別及轉送每項結果。傳送者和接收者使用相同的金鑰比對結果。
您可以透過下列兩種方式指定結果索引鍵:
- 明確索引鍵:您可以指定明確索引鍵 (例如
resultKey = "pickup_address")。傳回常見類型 (例如String、Boolean或原始類型) 時,或多個目的地傳回相同資料類型的不同例項時,請使用明確索引鍵。 - 類型衍生金鑰:如未指定明確金鑰,
ResultEventBus會使用結果類型的KClass(例如Contact::class.toString()) 的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 |
Queued:依序處理為鍵發出的所有結果。 | 一次性事件和副作用 (例如顯示 Snackbar 或轉送至 ViewModel)。 |
conflateAsState |
Conflated:捨棄中繼結果,僅保留最新結果做為 Compose State。 |
輕量 UI 狀態修飾符 (例如有效篩選器標記或選取項目覆寫)。 |
使用 ResultEffect 處理一次性事件
處理一次性事件時,請使用 ResultEffect,例如觸發數據分析、顯示 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>()) 清除結果。如要進一步瞭解索引鍵比對,請參閱「結果索引鍵」。
食譜
如需完整的可執行程式碼範例,瞭解不同的結果傳遞策略,請參閱下列範例: