Comprendere e implementare le nozioni di base

La navigazione descrive il modo in cui gli utenti si spostano all'interno della tua app. Gli utenti interagiscono con gli elementi UI, di solito toccandoli o facendoci clic sopra, e l'app risponde mostrando nuovi contenuti. Se l'utente vuole tornare ai contenuti precedenti, utilizza il gesto Indietro o tocca il pulsante Indietro.

Modellazione dello stato di navigazione

Un modo pratico per modellare questo comportamento è utilizzare uno stack di contenuti. Man mano che l'utente naviga in avanti verso nuovi contenuti, questi vengono inseriti in cima allo stack. Quando l'utente torna indietro da questi contenuti, questi vengono rimossi dallo stack e vengono visualizzati i contenuti precedenti. In termini di navigazione, questo stack viene solitamente chiamato back stack perché rappresenta i contenuti a cui l'utente può tornare.

Un pulsante di azione della tastiera su schermo (un'icona con un segno di spunta) cerchiato in rosso.
Figura 1. Diagramma che mostra come cambia il back stack con gli eventi di navigazione dell'utente.

Crea un back stack

In Navigation 3, il back stack non contiene effettivamente contenuti. Contiene invece riferimenti ai contenuti, noti come chiavi. Le chiavi possono essere di qualsiasi tipo, ma di solito sono classi di dati semplici e serializzabili. L'utilizzo di riferimenti anziché di contenuti presenta i seguenti vantaggi:

  • È semplice navigare inserendo le chiavi nel back stack.
  • Se le chiavi sono serializzabili, il back stack può essere salvato nello spazio di archiviazione permanente, in modo da sopravvivere alle modifiche alla configurazione e all'interruzione del processo. Questo è importante perché gli utenti si aspettano di uscire dall'app, tornare in un secondo momento e riprendere da dove avevano interrotto, con gli stessi contenuti visualizzati. Per saperne di più, consulta Salva il back stack.

Un concetto chiave dell'API Navigation 3 è che il back stack è di tua proprietà. La libreria:

  • Si aspetta che il tuo back stack sia un List<T> supportato dallo stato dello snapshot, dove T è il tipo di keys del tuo back stack. Puoi utilizzare Any o fornire le tue chiavi con un tipo più sicuro. Quando vedi i termini "push" o "pop", l'implementazione sottostante consiste nell'aggiungere o rimuovere elementi dalla fine di un elenco.
  • Osserva il back stack e ne riflette lo stato nell'interfaccia utente utilizzando un NavDisplay.

L'esempio seguente mostra come creare chiavi e un back stack e come modificare il back stack in risposta agli eventi di navigazione dell'utente:

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

Risolvere le chiavi per i contenuti

I contenuti vengono modellati in Navigation 3 utilizzando NavEntry, una classe contenente una funzione componibile. Rappresenta una destinazione, ovvero un singolo contenuto verso cui l'utente può navigare in avanti e indietro.

Un NavEntry può anche contenere metadati, ovvero informazioni sui contenuti. Questi metadati possono essere letti da oggetti contenitore, come NavDisplay, per aiutarli a decidere come visualizzare i contenuti di NavEntry. Ad esempio, i metadati possono essere utilizzati per sostituire le animazioni predefinite per un NavEntry specifico. NavEntry metadata è una mappa di chiavi String per i valori Any, che fornisce un'archiviazione dei dati versatile.

Per convertire un key in un NavEntry, crea un Entry Provider. Si tratta di una funzione che accetta un key e restituisce un NavEntry per quel key. In genere viene definito come parametro lambda durante la creazione di un NavDisplay.

Esistono due modi per creare un Entry Provider: creando direttamente una funzione lambda o utilizzando il DSL entryProvider.

Crea direttamente una funzione Entry Provider

In genere, crei una funzione Entry Provider utilizzando un'istruzione when, con un ramo per ciascuna delle tue chiavi.

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

Utilizza il DSL entryProvider

Il DSL entryProvider può semplificare la funzione lambda evitando la necessità di eseguire test su ciascuno dei tipi di chiave e di creare un NavEntry per ciascuno. Per farlo, utilizza la funzione builder entryProvider. Include anche il comportamento di riserva predefinito (generazione di un errore) se la chiave non viene trovata.

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

Nello snippet, tieni presente quanto segue:

  • entry viene utilizzato per definire un NavEntry con il tipo e i contenuti componibili specificati
  • entry accetta un parametro metadata per impostare NavEntry.metadata

Visualizza il back stack

Il back stack rappresenta lo stato di navigazione della tua app. Ogni volta che il back stack cambia, la UI dell'app deve riflettere il nuovo stato del back stack. In Navigation 3, un NavDisplay osserva il back stack e aggiorna la UI di conseguenza. Crealo con i seguenti parametri:

  • Il tuo back stack: deve essere di tipo SnapshotStateList<T>, dove T è il tipo delle chiavi del back stack. È un List osservabile, quindi attiva la ricomposizione di NavDisplay quando cambia.
  • Un entryProvider per convertire le chiavi nel back stack in oggetti NavEntry.
  • Facoltativamente, fornisci una funzione lambda al parametro onBack. Questo metodo viene chiamato quando l'utente attiva un evento Indietro.

L'esempio seguente mostra come creare un NavDisplay.

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

Per impostazione predefinita, NavDisplay mostra l'elemento NavEntry più in alto nel back stack in un layout a riquadro singolo. La seguente registrazione mostra l'app in esecuzione:

Comportamento predefinito di `NavDisplay` con due destinazioni.
Figura 2. Comportamento predefinito di NavDisplay con due destinazioni.

Ciclo di vita della destinazione

NavDisplay utilizza LifecycleOwner personalizzati per limitare lo stato del ciclo di vita di un NavEntry in base ai vincoli a livello di scena e di voce.

Per ulteriori informazioni sui cicli di vita in Compose, consulta Ciclo di vita in Jetpack Compose.

Vincoli del ciclo di vita a livello di scena

NavDisplay gestisce il ciclo di vita dei Scene attivi. I limiti a livello di scena vengono determinati come segue:

Per le scene non in overlay:

  • RESUMED: consentito solo quando la transizione della scena è terminata e non sono presenti scene in overlay attive visualizzate sopra.
  • STARTED: limitato a STARTED durante le transizioni della scena, ad esempio quando si naviga avanti o indietro o quando è coperto da un overlay.

Per le scene in overlay, come le finestre di dialogo o i riquadri inferiori:

  • RESUMED: consentito solo per la scena in overlay più in alto, attualmente attiva.
  • STARTED: limitato a STARTED per tutte le scene di overlay sottostanti coperte da un overlay più recente.

Stato del ciclo di vita di livello base

La libreria gestisce lo stato massimo del ciclo di vita di ogni singola NavEntry in base alla sua presenza nel back stack:

  • RESUMED: se la voce è presente nel back stack corrente, il suo ciclo di vita può arrivare fino a RESUMED (soggetto al limite a livello di scena).
  • CREATED: se la voce non è più nel back stack, ad esempio quando è stata rimossa, ma viene ancora visualizzata sullo schermo durante l'animazione, la libreria limita rigorosamente il suo ciclo di vita a CREATED. Questo limite garantisce che le voci in background o in uscita interrompano l'esecuzione di attività attive, come la raccolta di flussi o l'avvio di coroutine associate agli stati RESUMED o STARTED, mentre completano le transizioni di uscita.

Come si combinano

Ad esempio, lo stato finale del ciclo di vita di un NavEntry viene risolto come segue:

Scenario Limite a livello di scena Cappello Entry-Level Limite effettivo
Voce attiva, schermata fissa (senza transizioni o overlay) RESUMED RESUMED RESUMED
Voce attiva, durante la transizione (navigazione verso o da) STARTED RESUMED STARTED
Voce attiva, coperta da un overlay (ad esempio, una finestra di dialogo è aperta) STARTED RESUMED STARTED
Voce estratta, animazione in uscita STARTED o RESUMED CREATED CREATED

Elaborazione della risposta…

Il seguente diagramma mostra il flusso di dati tra i vari oggetti in Navigation 3:

Una visualizzazione del flusso di dati tra i vari oggetti in Navigation 3.
Figura 3. Diagramma che mostra il flusso di dati attraverso vari oggetti in Navigation 3.
  1. Gli eventi di navigazione avviano le modifiche. Le chiavi vengono aggiunte o rimosse dal back stack in risposta alle interazioni dell'utente.

  2. La modifica dello stato del back stack attiva il recupero dei contenuti. NavDisplay (un composable che esegue il rendering di un back stack) osserva il back stack. Nella sua configurazione predefinita, mostra la voce del back stack più in alto in un layout a riquadro singolo. Quando la chiave in cima al back stack cambia, NavDisplay la utilizza per richiedere i contenuti corrispondenti al provider di voci.

  3. Il fornitore di contenuti fornisce i contenuti. Il provider di voci è una funzione che risolve una chiave in un NavEntry. Quando riceve una chiave da NavDisplay, il provider di voci fornisce l'oggetto NavEntry associato, che contiene sia la chiave sia i contenuti.

  4. I contenuti vengono visualizzati. NavDisplay riceve NavEntry e visualizza i contenuti.