検索結果を返す

Navigation 3 1.2.0 以降では、ResultEventBus API を使用して、目的地から結果を返すことができます。

ResultEventBus には 2 つの通信モデルがあります。

  • イベントベースの結果: ResultEffect を使用して、一時的な 1 回限りのイベント(確認スナックバーの表示や副作用のトリガーなど)を処理します。
  • 状態ベースの結果: conflateAsState を使用して、Compose State として最新の結果を監視します。

結果イベントバスを設定する

コンポーザブル デスティネーションで ResultEventBus を使用できるようにするには、NavDisplay に渡されるデコレータのリストに rememberResultEventBusNavEntryDecorator を追加します。これにより、各デスティネーションのコンテンツに LocalResultEventBus 構成ローカルが提供されます。

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

結果キー

ResultEventBus は、キーを使用して各結果を識別し、ルーティングします。送信者と受信者は、同じキーを使用して結果を照合します。

結果キーは次の 2 つの方法で指定できます。

  • 明示的なキー: 明示的なキー(resultKey = "pickup_address" など)を指定できます。明示的なキーは、一般的な型(StringBoolean、プリミティブなど)を返す場合や、複数の宛先が同じデータ型の異なるインスタンスを返す場合に使用します。
  • 型派生キー: 明示的なキーを指定しない場合、ResultEventBus は結果型の KClassContact::class.toString() など)の toString 表現を使用してキーを自動的に生成します。型派生キーは、個別のドメイン固有のデータ型に使用します。

デスティネーションから結果を返す

画面コンポーザブルを再利用可能でテスト可能にするには、画面 UI 内で LocalResultEventBus に直接アクセスしないでください。代わりに、画面からコールバック ラムダを公開します。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 キューに登録済み: キーに対して出力されたすべての結果を順番に処理します。 1 回限りのイベントと副作用(スナックバーの表示や ViewModel への転送など)。
conflateAsState 統合: 中間結果を破棄し、最新の結果のみを Compose State として保持します。 軽量の UI 状態修飾子(アクティブなフィルタタグや選択のオーバーライドなど)。

ResultEffect で 1 回限りのイベントを処理する

ResultEffect は、アナリティクスのトリガー、スナックバーの表示、ViewModel への結果の転送など、1 回限りのイベントを処理する場合に使用します。

ResultEffect は、受信結果のキューを保持します。特定のキーに対して複数の結果が送信された場合、ResultEffect は送信された順序で各結果を処理します。また、ResultEffect はコルーチン スコープで実行されるため、エフェクト本体内で suspend 関数を直接呼び出すことができます。

明示的な結果キーに関連付けられた結果をリッスンできます。

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 で自動的に再コンポーズしたい場合は、ResultEventBusconflateAsState を呼び出します。

明示的な結果キーに関連付けられた結果を観察できます。

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
    )
}

Hoist ResultEventBus

デフォルトでは、rememberResultEventBusNavEntryDecoratorrememberResultEventBus を使用して内部的に独自の 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
    )
)

検索結果を管理、削除する

宛先が 1 回限りの結果を使用したら、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>())でクリアできます。キーのマッチングの詳細については、結果キーをご覧ください。

レシピ

さまざまな結果渡し戦略を示す実行可能なコード例については、次のレシピをご覧ください。