傳回結果

從 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")。傳回常見型別 (例如 StringBoolean 或原始型別),或多個目的地傳回相同資料型別的不同執行個體時,請使用明確鍵。
  • 類型衍生鍵:如果未指定明確的鍵,ResultEventBus 會自動使用結果類型 KClasstoString 表示法 (例如 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 會透過下列生命週期序列執行:

  1. 傳送者發出:傳送者目的地會使用 resultBus.sendResult(resultKey = "pickup_address", address) 傳送結果,並彈出返回堆疊。
  2. 接收器進入組合:接收器目的地會成為有效畫面,且 ResultEffect 開始監聽結果。
  3. 接收器處理結果ResultEffect 會接收並執行針對該鍵傳送的每個結果的效果主體,並依傳送順序處理所有發出的項目。
  4. 接收器離開組合:當接收器目的地從返回堆疊中彈出時,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>()) 清除結果。如要瞭解鍵比對的詳細資料,請參閱「結果鍵」。

食譜

如需完整的可執行程式碼範例,瞭解如何使用不同的結果傳遞策略,請參閱下列食譜: