Ergebnisse zurückgeben

Ab Navigation 3 1.2.0 können Sie Ergebnisse von Zielen mit der ResultEventBus API zurückgeben.

ResultEventBus bietet zwei Kommunikationsmodelle:

  • Ereignisbasierte Ergebnisse: Für vorübergehende, einmalige Ereignisse (z. B. das Anzeigen einer Bestätigungs-Snackbar oder das Auslösen einer Nebenwirkung) mit ResultEffect.
  • Zustandsbasierte Ergebnisse: Zum Beobachten des neuesten Ergebnisses als Compose-State mit conflateAsState.

Ergebnis-EventBus einrichten

Wenn Sie ResultEventBus für Ihre composable Ziele verfügbar machen möchten, fügen Sie den rememberResultEventBusNavEntryDecorator der Liste der Decorators hinzu, die an NavDisplay übergeben werden. Dadurch wird der Inhalt jedes Ziels mit einem LocalResultEventBus-CompositionLocal versehen.

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

Ergebnisschlüssel

ResultEventBus identifiziert und leitet jedes Ergebnis mithilfe eines Schlüssels weiter. Sender und Empfänger gleichen die Ergebnisse mit demselben Schlüssel ab.

Sie haben zwei Möglichkeiten, Ergebnisschlüssel anzugeben:

  • Explizite Schlüssel: Sie können einen expliziten Schlüssel (z. B. resultKey = "pickup_address") angeben. Verwenden Sie explizite Schlüssel, wenn Sie gängige Typen (z. B. String, Boolean oder Primitive) zurückgeben oder wenn mehrere Ziele unterschiedliche Instanzen desselben Datentyps zurückgeben.
  • Vom Typ abgeleitete Schlüssel: Wenn Sie keinen expliziten Schlüssel angeben, generiert ResultEventBus automatisch einen Schlüssel mit der toString-Darstellung des KClass des Ergebnistyps (z. B. Contact::class.toString()). Verwenden Sie vom Typ abgeleitete Schlüssel für eindeutige, domainspezifische Datentypen.

Ergebnisse von einem Ziel zurückgeben

Damit zusammensetzbare Funktionen für Bildschirme wiederverwendbar und testbar bleiben, sollten Sie nicht direkt in der Benutzeroberfläche des Bildschirms auf LocalResultEventBus zugreifen. Stattdessen sollten Sie Callback-Lambdas von Ihrem Bildschirm verfügbar machen. Verarbeite in deiner entryProvider den Callback, indem du das Ergebnis mit LocalResultEventBus.current sendest und zurücknavigierst.

Sie können Ergebnisse mit einem expliziten Ergebnisschlüssel senden:

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()
        }
    )
}

Sie können Ergebnisse auch mit einem vom Typ abgeleiteten Schlüssel senden:

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()
        }
    )
}

Ergebnisse erhalten

Ziele können Ergebnisse entweder über ereignisbasierte Effekte oder zustandsbasierte Observables nutzen.

API Verhalten Empfohlene Anwendungsfälle
ResultEffect In der Warteschlange: Alle für den Schlüssel ausgegebenen Ergebnisse werden der Reihe nach verarbeitet. Einmalige Ereignisse und Nebenwirkungen (z. B. Anzeigen einer Snackbar oder Weiterleiten an ein ViewModel).
conflateAsState Zusammengeführt: Zwischenergebnisse werden verworfen und nur das neueste Ergebnis wird als Compose-State beibehalten. Leichte UI-Zustandsmodifikatoren (z. B. aktive Filter-Tags oder Auswahlüberschreibungen).

Einmalige Ereignisse mit ResultEffect verarbeiten

Verwende ResultEffect, wenn du einmalige Ereignisse verarbeitest, z. B. das Auslösen von Analysen, das Anzeigen einer Snackbar oder das Weiterleiten eines Ergebnisses an ein ViewModel.

ResultEffect verwaltet eine Warteschlange für eingehende Ergebnisse. Wenn mehrere Ergebnisse für einen bestimmten Schlüssel gesendet werden, verarbeitet ResultEffect jedes Ergebnis in der Reihenfolge, in der es gesendet wurde. Außerdem wird ResultEffect in einem Koroutinenbereich ausgeführt, sodass Sie suspend-Funktionen direkt im Effekttext aufrufen können.

Sie können auf Ergebnisse warten, die mit einem expliziten Ergebnisschlüssel verknüpft sind:

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") }
    )
}

Sie können auch mit einem vom Typ abgeleiteten Schlüssel auf Ergebnisse warten:

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
    )
}

Wenn Sie zwischen Zielen wechseln, durchläuft ResultEffect die folgende Lebenszyklussequenz:

  1. Sender gibt aus: Das Senderziel sendet ein Ergebnis mit resultBus.sendResult(resultKey = "pickup_address", address) und entfernt das Element aus dem Back Stack.
  2. Empfänger wechselt in den Kompositionsmodus: Das Empfängerziel wird zum aktiven Bildschirm und ResultEffect wartet auf Ergebnisse.
  3. Empfänger verarbeitet Ergebnisse: ResultEffect empfängt und führt den Effekttext für jedes Ergebnis aus, das für diesen Schlüssel gesendet wurde. Dabei werden alle Emissionen in der Reihenfolge verarbeitet, in der sie gesendet wurden.
  4. Empfänger verlässt die Komposition: Wenn das Empfängerziel aus dem Back Stack entfernt wird, verlässt ResultEffect die Komposition und wartet nicht mehr auf Ergebnisse.

Aktuelle Ergebnisse als Status mit conflateAsState beobachten

Wenn Sie nur den neuesten Ergebniswert benötigen, um den lokalen UI-Status direkt zu ändern oder zu filtern, und möchten, dass Compose bei jeder Aktualisierung des Ergebnisses automatisch eine Neuzusammensetzung vornimmt, rufen Sie conflateAsState für ResultEventBus auf.

Sie können Ergebnisse beobachten, die mit einem expliziten Ergebnisschlüssel verknüpft sind:

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") }
    )
}

Sie können die Ergebnisse auch mit einem vom Typ abgeleiteten Schlüssel beobachten:

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

Standardmäßig erstellt und speichert rememberResultEventBusNavEntryDecorator intern einen eigenen ResultEventBus mit rememberResultEventBus.

In folgenden Fällen können Sie einen ResultEventBus explizit erstellen und hochstufen:

  • Übergeben Sie die ResultEventBus-Instanz direkt an nicht kompositionsfähige Komponenten oder Diagramme zur Abhängigkeitsinjektion.
  • Ergebnisse von der App-Struktur auf oberster Ebene (z. B. App-Leiste oder Navigationsleiste) außerhalb der Zielhierarchie senden oder beobachten.

Wenn Sie ResultEventBus hochstufen möchten, erstellen Sie es mit rememberResultEventBus und übergeben Sie es an 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
    )
)

Ergebnisse verwalten und löschen

Wenn ein Ziel ein einmaliges Ergebnis verarbeitet, löschen Sie es mit removeResult aus dem Ereignisbus. Dadurch wird verhindert, dass der Bus vergangene Ereignisse noch einmal an neue Beobachter liefert, wenn Ziele wieder in die Komposition aufgenommen werden:

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")
    }
}

Sie können Ergebnisse nach explizitem Schlüssel (resultBus.removeResult(resultKey)) oder nach typabgeleitetem Schlüssel (resultBus.removeResult<T>()) löschen. Weitere Informationen zum Schlüsselabgleich finden Sie unter Ergebnisschlüssel.

Rezepte

Vollständige ausführbare Codebeispiele, die verschiedene Strategien zur Ergebnisübergabe veranschaulichen, finden Sie in den folgenden Rezepten: