Возвращаемые результаты

Начиная с версии 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 выполняется в следующем порядке:

  1. Отправитель передает данные. Целевой объект отправителя отправляет результат с помощью resultBus.sendResult(resultKey = "pickup_address", address) и удаляет элементы из стека возврата.
  2. Получатель переходит в режим создания. Экран получателя становится активным, и ResultEffect начинает ждать результатов.
  3. Получатель обрабатывает результаты. ResultEffect получает и выполняет тело эффекта для каждого результата, отправленного для этого ключа, обрабатывая все выбросы в порядке их отправки.
  4. Получатель покидает композицию. Когда получатель удаляется из стека, 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>()). Подробнее о сопоставлении ключей рассказывается в разделе Ключи результатов.

Рецепты

Полные примеры кода, демонстрирующие различные стратегии передачи результатов, приведены в следующих рецептах: