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

Начиная с Navigation 3 1.2.0, вы можете возвращать результаты из пунктов назначения с помощью API ResultEventBus.

В ResultEventBus предусмотрено две модели взаимодействия:

  • Результаты на основе событий. Для временных однократных событий (например, показа всплывающего уведомления или запуска побочного эффекта) используйте ResultEffect.
  • Результаты на основе состояния. Чтобы отслеживать последний результат в виде State Compose, используйте 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 выполняет следующую последовательность жизненного цикла:

  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>()). Подробные сведения о сопоставлении ключей приведены в разделе Ключи результатов.

Рецепты

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