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