Начиная с Navigation 3 1.2.0, вы можете возвращать результаты из пунктов назначения с помощью API ResultEventBus.
В ResultEventBus предусмотрено две модели взаимодействия:
- Результаты на основе событий. Для временных однократных событий (например, показа всплывающего уведомления или запуска побочного эффекта) используйте
ResultEffect. - Результаты на основе состояния. Чтобы отслеживать последний результат в виде
StateCompose, используйтеconflateAsState.
Настройка шины событий результатов
Чтобы сделать ResultEventBus доступным для ваших composable-целей, добавьте rememberResultEventBusNavEntryDecorator в список декораторов, передаваемых в NavDisplay. Это позволяет каждому разделу иметь свой LocalResultEventBus.
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
Ключи результатов
ResultEventBus идентифицирует и маршрутизирует каждый результат с помощью ключа. Отправители и получатели сопоставляют результаты, используя один и тот же ключ.
Указать ключи результатов можно двумя способами:
- Явные ключи. Вы можете указать явный ключ (например,
resultKey = "pickup_address"). Используйте явные ключи при возврате распространенных типов (таких какString,Booleanили примитивы) или когда несколько целевых объектов возвращают разные экземпляры одного и того же типа данных. - Ключи, производные от типа. Если вы не укажете ключ явным образом,
ResultEventBusавтоматически сгенерирует его, используяtoStringтипа результатаKClass(например,Contact::class.toString()). Используйте ключи, производные от типа, для отдельных типов данных, относящихся к домену.
Как вернуть результаты из пункта назначения
Чтобы composable-функции экрана можно было использовать повторно и тестировать, не обращайтесь к 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. Все результаты, полученные для ключа, обрабатываются по порядку. | Однократные события и побочные эффекты (например, отображение всплывающего уведомления или перенаправление на ViewModel). |
conflateAsState |
Conflated. Промежуточные результаты отбрасываются, и сохраняется только последний результат в виде Compose State. |
Легковесные модификаторы состояния интерфейса (например, теги активных фильтров или переопределения выбора). |
Как обрабатывать однократные события с помощью ResultEffect
Используйте ResultEffect для обработки однократных событий, таких как запуск аналитики, показ всплывающего уведомления или передача результата в 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 выполняет следующую последовательность жизненного цикла:
- Отправитель передает данные. Пункт назначения отправляет результат с помощью
resultBus.sendResult(resultKey = "pickup_address", address)и извлекает обратный стек. - Получатель входит в композицию. Целевой получатель становится активным экраном, и
ResultEffectначинает ожидать результатов. - Получатель обрабатывает результаты.
ResultEffectполучает и выполняет тело эффекта для каждого результата, отправленного для этого ключа, обрабатывая все передачи в том порядке, в котором они были отправлены. - Получатель покидает композицию. Когда целевой объект получателя удаляется из обратного стека,
ResultEffectпокидает композицию и перестает ожидать результаты.
Отслеживайте последние результаты как состояние с помощью conflateAsState
Если вам нужно только последнее значение результата, чтобы напрямую изменить или отфильтровать состояние локального интерфейса, и вы хотите, чтобы Compose автоматически выполнял рекомпозицию при каждом обновлении результата, вызовите conflateAsState для ResultEventBus.
Вы можете наблюдать за результатами, связанными с явным ключом результата:
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 создает и запоминает собственный ResultEventBus, используя rememberResultEventBus.
Вы можете явно создать и поднять 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>()). Подробные сведения о сопоставлении ключей приведены в разделе Ключи результатов.
Рецепты
Полные примеры кода, демонстрирующие разные стратегии передачи результатов, можно найти в следующих рецептах:
- Рецепт результатов на основе событий
- Рецепт для результатов на основе штата
- Рецепт сериализуемого результата