Grundlagen verstehen und implementieren

Die Navigation beschreibt, wie sich Nutzer in Ihrer App bewegen. Sie interagieren mit UI-Elementen, in der Regel durch Tippen oder Klicken darauf, und die App reagiert mit der Anzeige neuer Inhalte. Wenn der Nutzer zum vorherigen Inhalt zurückkehren möchte, verwendet er die Zurück-Geste oder tippt auf den Button „Zurück“.

Navigationsstatus modellieren

Eine praktische Möglichkeit, dieses Verhalten zu modellieren, ist ein Stapel von Inhalten. Wenn der Nutzer vorwärts zu neuen Inhalten navigiert, werden diese oben auf den Stapel gelegt. Wenn er zurück von diesen Inhalten navigiert, werden sie vom Stapel entfernt und der vorherige Inhalt wird angezeigt. In der Navigation wird dieser Stapel normalerweise als Back-Stack bezeichnet, da er den Inhalt darstellt, zu dem der Nutzer zurückkehren kann.

Eine Softwaretastatur-Schaltfläche (ein Häkchensymbol), die rot umrandet ist.
Abbildung 1. Diagramm, das zeigt, wie sich der Back-Stack mit Navigationsereignissen des Nutzers ändert.

Back-Stack erstellen

In Navigation 3 enthält der Back-Stack keine Inhalte. Stattdessen enthält er Verweise auf Inhalte, die als Schlüssel bezeichnet werden. Schlüssel können einen beliebigen Typ haben, sind aber in der Regel einfache, serialisierbare Datenklassen. Die Verwendung von Verweisen anstelle von Inhalten hat folgende Vorteile:

  • Die Navigation ist einfach, da Schlüssel auf den Back-Stack gelegt werden.
  • Solange die Schlüssel serialisierbar sind, kann der Back-Stack im nichtflüchtigen Speicher gespeichert werden, sodass er Konfigurationsänderungen und die Prozessbeendigung übersteht. Das ist wichtig, da Nutzer erwarten, dass sie Ihre App verlassen, später zurückkehren und dort weitermachen können, wo sie aufgehört haben, wobei derselbe Inhalt angezeigt wird. Weitere Informationen finden Sie unter Back-Stack speichern.

Ein wichtiges Konzept in der Navigation 3 API ist, dass Sie der Inhaber des Back-Stacks sind. Die Bibliothek:

  • Erwartet, dass Ihr Back-Stack eine Snapshot-Status-gestützte List<T> ist, wobei T der Typ Ihres Back-Stack keys ist. Sie können Any verwenden oder eigene, stärker typisierte Schlüssel bereitstellen. Wenn Sie die Begriffe „push“ oder „pop“ sehen, werden Elemente am Ende einer Liste hinzugefügt oder entfernt.
  • Beobachtet Ihren Back-Stack und spiegelt seinen Status in der UI mit einem NavDisplay wider.

Das folgende Beispiel zeigt, wie Sie Schlüssel und einen Back-Stack erstellen und den Back-Stack als Reaktion auf Navigationsereignisse des Nutzers ändern:

// Define keys that will identify content
data object ProductList
data class ProductDetail(val id: String)

@Composable
fun MyApp() {

    // Create a back stack, specifying the key the app should start with
    val backStack = remember { mutableStateListOf<Any>(ProductList) }

    // Supply your back stack to a NavDisplay so it can reflect changes in the UI
    // ...more on this below...

    // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state
    backStack.add(ProductDetail(id = "ABC"))

    // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state
    backStack.removeLastOrNull()
}

Schlüssel in Inhalte auflösen

Inhalte werden in Navigation 3 mit NavEntry modelliert. Das ist eine Klasse, die eine zusammensetzbare Funktion enthält. Sie stellt ein Ziel dar – einen einzelnen Inhalt , zu dem der Nutzer vorwärts und zurück navigieren kann.

Ein NavEntry kann auch Metadaten enthalten – Informationen zum Inhalt. Diese Metadaten können von Containerobjekten wie NavDisplay gelesen werden, um zu entscheiden, wie der Inhalt von NavEntry angezeigt werden soll. Metadaten können beispielsweise verwendet werden, um die Standardanimationen für einen bestimmten NavEntry zu überschreiben. metadata von NavEntry ist eine Map von String-Schlüsseln zu Any-Werten, die eine vielseitige Datenspeicherung ermöglicht.

Um einen key in einen NavEntry zu konvertieren, erstellen Sie einen Eintragsanbieter. Das ist eine Funktion, die einen key akzeptiert und einen NavEntry für diesen key zurückgibt. Sie wird normalerweise als Lambda-Parameter definiert, wenn ein NavDisplay erstellt wird.

Es gibt zwei Möglichkeiten, einen Eintragsanbieter zu erstellen: entweder direkt eine Lambda Funktion erstellen oder die entryProvider-DSL verwenden.

Eintragsanbieterfunktion direkt erstellen

Normalerweise erstellen Sie eine Eintragsanbieterfunktion mit einer when-Anweisung, mit einem Zweig für jeden Ihrer Schlüssel.

entryProvider = { key ->
    when (key) {
        is ProductList -> NavEntry(key) { Text("Product List") }
        is ProductDetail -> NavEntry(
            key,
            metadata = mapOf("extraDataKey" to "extraDataValue")
        ) { Text("Product ${key.id} ") }

        else -> {
            NavEntry(Unit) { Text(text = "Invalid Key: $it") }
        }
    }
}

entryProvider-DSL verwenden

Die entryProvider-DSL kann Ihre Lambda-Funktion vereinfachen, da Sie nicht für jeden Ihrer Schlüsseltypen testen und für jeden einen NavEntry erstellen müssen. Verwenden Sie dazu die Builder-Funktion entryProvider. Sie enthält auch ein Standard-Fallbackverhalten (Fehler auslösen), wenn der Schlüssel nicht gefunden wird.

entryProvider = entryProvider {
    entry<ProductList> { Text("Product List") }
    entry<ProductDetail>(
        metadata = mapOf("extraDataKey" to "extraDataValue")
    ) { key -> Text("Product ${key.id} ") }
}

Beachten Sie Folgendes im Snippet:

  • entry wird verwendet, um einen NavEntry mit dem angegebenen Typ und zusammensetzbaren Inhalt zu definieren.
  • entry akzeptiert einen metadata-Parameter, um NavEntry.metadata festzulegen.

Back-Stack anzeigen

Der Back-Stack stellt den Navigationsstatus Ihrer App dar. Jedes Mal, wenn sich der Back-Stack ändert, sollte die App-UI den neuen Back-Stack-Status widerspiegeln. In Navigation 3 beobachtet ein NavDisplay Ihren Back-Stack und aktualisiert die UI entsprechend. Erstellen Sie ihn mit den folgenden Parametern:

  • Ihr Back-Stack – er sollte vom Typ SnapshotStateList<T> sein, wobei T der Typ Ihrer Back-Stack-Schlüssel ist. Es ist eine beobachtbare List, sodass bei Änderungen eine Neukomposition von NavDisplay ausgelöst wird.
  • Ein entryProvider, um die Schlüssel in Ihrem Back-Stack in NavEntry-Objekte zu konvertieren.
  • Optional können Sie ein Lambda für den Parameter onBack angeben. Dieser wird aufgerufen, wenn der Nutzer ein Zurück-Ereignis auslöst.

Das folgende Beispiel zeigt, wie Sie ein NavDisplay erstellen:

data object Home
data class Product(val id: String)

@Composable
fun NavExample() {

    val backStack = remember { mutableStateListOf<Any>(Home) }

    NavDisplay(
        backStack = backStack,
        onBack = { backStack.removeLastOrNull() },
        entryProvider = { key ->
            when (key) {
                is Home -> NavEntry(key) {
                    ContentGreen("Welcome to Nav3") {
                        Button(onClick = {
                            backStack.add(Product("123"))
                        }) {
                            Text("Click to navigate")
                        }
                    }
                }

                is Product -> NavEntry(key) {
                    ContentBlue("Product ${key.id} ")
                }

                else -> NavEntry(Unit) { Text("Unknown route") }
            }
        }
    )
}

Standardmäßig zeigt NavDisplay den obersten NavEntry im Back-Stack in einem Layout mit einem einzelnen Bereich an. Die folgende Aufzeichnung zeigt diese App in Aktion:

Standardverhalten von „NavDisplay“ mit zwei Zielen.
Abbildung 2. NavDisplay Standardverhalten mit zwei Zielen.

Lebenszyklus des Ziels

NavDisplay verwendet benutzerdefinierte LifecycleOwners, um den Lebenszyklusstatus eines NavEntry sowohl basierend auf Einschränkungen auf Szenen-Ebene als auch auf Einschränkungen auf Eintragsebene zu begrenzen.

Weitere Informationen zu Lebenszyklen in Compose finden Sie unter Lebenszyklus in Jetpack Compose.

Einschränkungen des Lebenszyklus auf Szenenebene

NavDisplay verwaltet den Lebenszyklus der aktiven Scenes. Die Einschränkungen auf Szenenebene werden so bestimmt:

Für Szenen ohne Overlay:

  • RESUMED: Nur zulässig, wenn die Szenenübergänge abgeschlossen sind und keine aktiven Overlay-Szenen darüber angezeigt werden.
  • STARTED: Auf STARTED begrenzt, während die Szenenübergänge stattfinden, z. B. beim Vorwärts- oder Zurücknavigieren oder wenn sie von einem Overlay verdeckt werden.

Für Overlay-Szenen wie Dialogfelder oder Bottom Sheets:

  • RESUMED: Nur für die oberste, derzeit aktive Overlay-Szene zulässig.
  • STARTED: Auf STARTED begrenzt für alle darunter liegenden Overlay-Szenen, die von einem neueren Overlay verdeckt werden.

Lebenszyklusstatus auf Eintragsebene

Die Bibliothek verwaltet den maximalen Lebenszyklusstatus jedes einzelnen NavEntry basierend auf seiner Position im Back-Stack:

  • RESUMED: Wenn der Eintrag im aktuellen Back-Stack vorhanden ist, kann sein Lebenszyklus bis zu RESUMED reichen (vorbehaltlich der Einschränkung auf Szenenebene).
  • CREATED: Wenn sich der Eintrag nicht mehr im Back-Stack befindet, z. B. wenn er entfernt wurde, aber während der Animation noch auf dem Bildschirm gerendert wird, begrenzt die Bibliothek seinen Lebenszyklus streng auf CREATED. Diese Einschränkung sorgt dafür, dass Hintergrund- oder Beendigungseinträge die Ausführung aktiver Aufgaben wie das Erfassen von Abläufen oder das Starten von Coroutinen, die an die Status RESUMED oder STARTED gebunden sind, beenden, während sie ihre Beendigungsübergänge abschließen.

Kombination

Der endgültige Lebenszyklusstatus eines NavEntry wird beispielsweise so bestimmt:

Szenario Einschränkung auf Szenenebene Einschränkung auf Eintragsebene Effektive Einschränkung
Aktiver Eintrag, Bildschirm fertig (keine Übergänge oder Overlays) RESUMED RESUMED RESUMED
Aktiver Eintrag, während des Übergangs (Navigieren zu oder von) STARTED RESUMED STARTED
Aktiver Eintrag, von einem Overlay verdeckt (z. B. ein Dialogfeld ist geöffnet) STARTED RESUMED STARTED
Entfernter Eintrag, Animation läuft STARTED oder RESUMED CREATED CREATED

Zusammenfassung

Das folgende Diagramm zeigt, wie Daten zwischen den verschiedenen Objekten in Navigation 3 fließen:

Eine Visualisierung des Datenflusses zwischen den verschiedenen Objekten in Navigation 3.
Abbildung 3. Diagramm, das zeigt, wie Daten durch verschiedene Objekte in Navigation 3 fließen.
  1. Navigationsereignisse lösen Änderungen aus. Als Reaktion auf Nutzerinteraktionen werden Schlüssel zum Back-Stack hinzugefügt oder daraus entfernt.

  2. Änderungen am Back-Stack-Status lösen das Abrufen von Inhalten aus. Das NavDisplay (eine zusammensetzbare Funktion, die einen Back-Stack rendert) beobachtet den Back-Stack. In der Standardkonfiguration wird der oberste Back-Stack-Eintrag in einem Layout mit einem einzelnen Bereich angezeigt. Wenn sich der oberste Schlüssel im Back-Stack ändert, verwendet NavDisplay diesen Schlüssel, um den entsprechenden Inhalt vom Eintragsanbieter anzufordern.

  3. Eintragsanbieter liefert Inhalte. Der Eintragsanbieter ist eine Funktion, die einen Schlüssel in einen NavEntry auflöst. Nachdem der Eintragsanbieter einen Schlüssel von NavDisplay erhalten hat, liefert er den zugehörigen NavEntry, der sowohl den Schlüssel als auch den Inhalt enthält.

  4. Inhalte werden angezeigt. NavDisplay empfängt den NavEntry und zeigt den Inhalt an.