Ergebnisse zurückgeben

Ab Navigation 3 1.2.0 können Sie mit der ResultEventBus API Ergebnisse von Zielen 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 eines Nebeneffekts) wird ResultEffect verwendet.
  • Statusbasierte Ergebnisse: Hier können Sie das letzte Ergebnis von Compose State mit conflateAsState beobachten.

Ergebnis-Eventbus einrichten

Wenn Sie ResultEventBus für Ihre zusammensetzbaren Ziele verfügbar machen möchten, fügen Sie rememberResultEventBusNavEntryDecorator der Liste der Dekoratoren hinzu, die an Ihre NavDisplay übergeben werden. So wird für die Inhalte jedes Ziels eine LocalResultEventBus-Komposition lokal bereitgestellt.

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

Ergebnisschlüssel

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

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

  • Explizite Schlüssel: Sie können einen expliziten Schlüssel angeben, z. B. resultKey = "pickup_address". Verwenden Sie explizite Schlüssel, wenn Sie gängige Typen (z. B. String, Boolean oder Primitiven) zurückgeben oder wenn mehrere Ziele unterschiedliche Instanzen desselben Datentyps zurückgeben.
  • Von 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 von Typ abgeleitete Schlüssel für unterschiedliche, domänenspezifische Datentypen.

Ergebnisse aus einem Ziel zurückgeben

Damit Bildschirm-Composables wiederverwendbar und testbar bleiben, sollten Sie nicht direkt in der Benutzeroberfläche des Bildschirms auf LocalResultEventBus zugreifen. Stattdessen sollten Sie Callback-Lambdas über Ihren Bildschirm verfügbar machen. Verarbeiten Sie in Ihrem entryProvider den Callback, indem Sie das Ergebnis mit LocalResultEventBus.current senden und zurücknavigieren.

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 typabgeleiteten 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

Für Zielvorhaben können Ergebnisse entweder mit ereignisbasierten Effekten oder mit zustandsbasierten beobachtbaren Variablen verwendet werden.

API Verhalten Empfohlene Anwendungsfälle
ResultEffect In der Warteschlange: Alle für den Schlüssel ausgegebenen Ergebnisse werden in der richtigen Reihenfolge verarbeitet. Einmalige Ereignisse und Nebeneffekte (z. B. Anzeigen einer Snackbar oder Weiterleitung zu ViewModel).
conflateAsState Zusammengeführt: Zwischenergebnisse werden verworfen und nur das letzte Ergebnis wird als Compose State beibehalten. Leichte UI-Statusmodifizierer wie aktive Filter-Tags oder Auswahlüberschreibungen.

Einmalige Ereignisse mit ResultEffect verarbeiten

Verwenden Sie ResultEffect, wenn Sie einmalige Ereignisse wie das Auslösen von Analysen, das Anzeigen einer Snackbar oder das Weiterleiten eines Ergebnisses an ein ViewModel verarbeiten.

ResultEffect verwaltet eine Warteschlange für eingehende Ergebnisse. Wenn für einen bestimmten Schlüssel mehrere Ergebnisse 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 Effekt-Body 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 typabgeleiteten 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
    )
}

Beim Navigieren zwischen Zielen wird ResultEffect in der folgenden Lebenszyklussequenz ausgeführt:

  1. Absender gibt aus: Das Ziel des Absenders sendet ein Ergebnis mit resultBus.sendResult(resultKey = "pickup_address", address) und entfernt den Backstack.
  2. Empfänger ruft Komposition auf: Das Empfängerziel wird zum aktiven Bildschirm und ResultEffect beginnt, auf Ergebnisse zu warten.
  3. Empfänger verarbeitet Ergebnisse: ResultEffect empfängt und führt den Effektkörper für jedes Ergebnis aus, das für diesen Schlüssel gesendet wird. 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 Backstack entfernt wird, verlässt ResultEffect die Komposition und beendet das Abrufen von Ergebnissen.

Letzte 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 Compose automatisch neu komponieren soll, wenn das Ergebnis aktualisiert wird, 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 Ergebnisse auch mit einem typabgeleiteten 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.

Sie können ein ResultEventBus explizit erstellen und hochladen, wenn Sie Folgendes benötigen:

  • Übergeben Sie die ResultEventBus-Instanz direkt an nicht zusammensetzbare Komponenten oder Dependency Injection-Diagramme.
  • Ergebnisse aus dem App-Scaffolding der obersten Ebene (z. B. App-Leiste oder Navigationsleiste) außerhalb der Zielhierarchie senden oder beobachten.

Um ResultEventBus zu verschieben, 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 verwendet, entfernen Sie es mit removeResult aus dem Eventbus. So wird verhindert, dass der Bus vergangene Ereignisse noch einmal an neue Beobachter sendet, 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 für verschiedene Strategien zum Übergeben von Ergebnissen finden Sie in den folgenden Rezepten: