傳回結果

自 Navigation 3 1.2.0 起,您可以使用 ResultEventBus API 從目的地傳回結果。

ResultEventBus 提供兩種通訊模式:

  • 事件型結果:使用 ResultEffect 處理暫時性的一次性事件 (例如顯示確認 Snackbar 或觸發副作用)。
  • 以狀態為基礎的結果:使用 conflateAsState 觀察 Compose State 的最新結果。

設定結果事件匯流排

如要讓可組合目的地使用 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 會依下列生命週期順序執行:

  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>()) 清除結果。如要進一步瞭解索引鍵比對,請參閱「結果索引鍵」。

食譜

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