Navigation 3 1.2.0 以降では、ResultEventBus API を使用して、目的地から結果を返すことができます。
ResultEventBus には 2 つの通信モデルがあります。
- イベントベースの結果:
ResultEffectを使用して、一時的な 1 回限りのイベント(確認スナックバーの表示や副作用のトリガーなど)を処理します。 - 状態ベースの結果:
conflateAsStateを使用して、ComposeStateとして最新の結果を監視します。
結果イベントバスを設定する
コンポーザブル デスティネーションで 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 |
キューに登録済み: キーに対して出力されたすべての結果を順番に処理します。 | 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 は次のライフサイクル シーケンスで実行されます。
- 送信側が送信: 送信側のデスティネーションが
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 ) }
Hoist 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>())でクリアできます。キーのマッチングの詳細については、結果キーをご覧ください。
レシピ
さまざまな結果渡し戦略を示す実行可能なコード例については、次のレシピをご覧ください。