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

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

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

  • Результаты, основанные на событиях : для кратковременных, разовых событий (например, отображения всплывающего окна подтверждения или запуска побочного эффекта) используется ResultEffect .
  • Результаты, основанные на состоянии : Для просмотра последнего результата в качестве State композиции с помощью conflateAsState .

Настройте шину событий результатов.

Чтобы сделать ResultEventBus доступным для ваших компонуемых целевых объектов, добавьте rememberResultEventBusNavEntryDecorator в список декораторов, передаваемых в ваш NavDisplay . Это обеспечит содержимому каждого целевого объекта локальную композицию LocalResultEventBus .

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

Ключи результатов

ResultEventBus идентифицирует и направляет каждый результат, используя ключ. Отправители и получатели сопоставляют результаты, используя один и тот же ключ.

Ключи результатов можно указать двумя способами:

  • Явные ключи : Вы можете указать явный ключ (например, resultKey = "pickup_address" ). Используйте явные ключи при возврате распространенных типов данных (таких как String , Boolean или примитивные типы) или когда несколько пунктов назначения возвращают разные экземпляры одного и того же типа данных.
  • Ключи, определяемые типом : Если вы не указываете явный ключ, ResultEventBus автоматически генерирует ключ, используя представление типа результата в KClass toString (например, 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 Conflated : Отбрасывает промежуточные результаты и сохраняет только последний результат в качестве 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
    )
}

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

Рецепты

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