検索結果を返す

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" など)を指定できます。明示的なキーは、一般的な型(String、Boolean、プリミティブなど)を返す場合や、複数のデスティネーションが同じデータ型の異なるインスタンスを返す場合に使用します。
  • 型から派生したキー: 明示的なキーを指定しない場合、ResultEventBus は、結果の型の KClass(Contact::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 Queued: キーに対して出力されたすべての結果を順番に処理します。 1 回限りのイベントと副作用(スナックバーの表示や ViewModel への転送など)。
conflateAsState Conflated: 中間結果を破棄し、最新の結果のみを Compose State として保持します。 軽量の UI 状態修飾子(アクティブなフィルタタグや選択のオーバーライドなど)。

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

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

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 で自動的に再コンポーズする場合は、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
    )
)

結果を管理、消去する

デスティネーションが 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>())で消去できます。キーのマッチングの詳細については、結果キーをご覧ください。

レシピ

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