Comprende y, luego, implementa los conceptos básicos

La navegación describe la forma en que los usuarios se mueven por tu app. Los usuarios interactúan con los elementos de la IU, por lo general, presionando o haciendo clic en ellos, y la app responde mostrando contenido nuevo. Si el usuario quiere volver al contenido anterior, usa el gesto atrás o presiona el botón Atrás.

Cómo modelar el estado de navegación

Una forma conveniente de modelar este comportamiento es con una pila de contenido. A medida que el usuario navega hacia adelante para ver contenido nuevo, este se envía a la parte superior de la pila. Cuando el usuario vuelve atrás desde ese contenido, se quita de la pila y se muestra el contenido anterior. En términos de navegación, esta pila suele denominarse pila de actividades porque representa el contenido al que el usuario puede volver.

Un botón de acción del teclado en pantalla (un ícono de marca de verificación) encerrado en un círculo rojo.
Figura 1. Diagrama que muestra cómo cambia la pila de actividades con los eventos de navegación del usuario

Cómo crear una pila de actividades

En Navigation 3, la pila de actividades no contiene contenido. En su lugar, contiene referencias al contenido, conocidas como claves. Las claves pueden ser de cualquier tipo, pero suelen ser clases de datos serializables y simples. Usar referencias en lugar de contenido tiene los siguientes beneficios:

  • Es fácil navegar enviando claves a la pila de actividades.
  • Siempre que las claves sean serializables, la pila de actividades se puede guardar en el almacenamiento persistente, lo que le permite sobrevivir a los cambios de configuración y a la finalización del proceso. Esto es importante porque los usuarios esperan salir de tu app, volver a ella más tarde y continuar donde la dejaron con el mismo contenido que se muestra. Consulta Cómo guardar tu pila de actividades para obtener más información.

Un concepto clave en la API de Navigation 3 es que eres propietario de la pila de actividades. La biblioteca:

  • Espera que tu pila de actividades sea un List<T> respaldado por el estado de instantánea, donde T es el tipo de tus keys de pila de actividades. Puedes usar Any o proporcionar tus propias claves con tipos más definidos. Cuando veas los términos "push" o "pop", la implementación subyacente es agregar o quitar elementos del final de una lista.
  • Observa tu pila de actividades y refleja su estado en la IU con un NavDisplay.

En el siguiente ejemplo, se muestra cómo crear claves y una pila de actividades, y cómo modificar la pila de actividades en respuesta a los eventos de navegación del usuario:

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

Cómo resolver claves para el contenido

El contenido se modela en Navigation 3 con NavEntry, que es una clase que contiene una función de componibilidad. Representa un destino , es decir, una sola pieza de contenido a la que el usuario puede navegar hacia adelante y hacia atrás.

Un NavEntry también puede contener metadatos, es decir, información sobre el contenido. Los objetos de contenedor, como NavDisplay, pueden leer estos metadatos para ayudarlos a decidir cómo mostrar el contenido de NavEntry. Por ejemplo, los metadatos se pueden usar para anular las animaciones predeterminadas de un NavEntry específico. NavEntry metadata es un mapa de claves String para valores Any, lo que proporciona un almacenamiento de datos versátil.

Para convertir una key en un NavEntry, crea un proveedor de entradas. Esta es una función que acepta una key y muestra un NavEntry para esa key. Por lo general, se define como un parámetro lambda cuando se crea un NavDisplay.

Existen dos maneras de crear un proveedor de entradas: crear una función lambda directamente o usar el entryProvider DSL.

Cómo crear una función de proveedor de entradas directamente

Por lo general, creas una función de proveedor de entradas con una instrucción when, con una rama para cada una de tus claves.

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

Cómo usar el DSL entryProvider

El DSL entryProvider puede simplificar tu función lambda, ya que evita la necesidad de probar cada uno de tus tipos de clave y construir un NavEntry para cada uno. Usa la función de compilador entryProvider para esto. También incluye un comportamiento de resguardo predeterminado (generar un error) si no se encuentra la clave.

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

Ten en cuenta lo siguiente del fragmento:

  • entry se usa para definir un NavEntry con el tipo y el contenido componible determinados.
  • entry acepta un parámetro metadata para establecer NavEntry.metadata.

Cómo mostrar la pila de actividades

La pila de actividades representa el estado de navegación de tu app. Cada vez que cambia la pila de actividades, la IU de la app debe reflejar el nuevo estado de la pila de actividades. En Navigation 3, un NavDisplay observa tu pila de actividades y actualiza su IU según corresponda. Constrúyelo con los siguientes parámetros:

  • Tu pila de actividades: Debe ser del tipo SnapshotStateList<T>, donde T es el tipo de tus claves de pila de actividades. Es un List observable, por lo que activa la recomposición de NavDisplay cuando cambia.
  • Un entryProvider para convertir las claves de tu pila de actividades en objetos NavEntry.
  • De manera opcional, proporciona una lambda al parámetro onBack. Se llama cuando el usuario activa un evento atrás.

En el siguiente ejemplo, se muestra cómo crear 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") }
            }
        }
    )
}

De forma predeterminada, el NavDisplay muestra el NavEntry superior en la pila de actividades en un diseño de un solo panel. En la siguiente grabación, se muestra la ejecución de esta app:

Comportamiento predeterminado de `NavDisplay` con dos destinos.
Figura 2. NavDisplay comportamiento predeterminado con dos destinos.

Ciclo de vida del destino

NavDisplay usa personalizados LifecycleOwners para limitar el estado del ciclo de vida de un NavEntry en función de las restricciones a nivel de la escena y a nivel de la entrada restricciones.

Para obtener más información sobre los ciclos de vida en Compose, consulta Ciclo de vida en Jetpack Compose.

Restricciones del ciclo de vida a nivel de la escena

NavDisplay administra el ciclo de vida de los Scenes activos. Los límites a nivel de la escena se determinan de la siguiente manera:

Para escenas que no son de superposición:

  • RESUMED: Se permite solo cuando la transición de la escena se haya establecido y no haya escenas de superposición activas que se muestren sobre ella.
  • STARTED: Se limita a STARTED mientras la escena realiza la transición, por ejemplo, cuando se navega hacia adelante o hacia atrás, o cuando está cubierta por una superposición.

Para escenas de superposición, como diálogos o hojas inferiores:

  • RESUMED: Se permite solo para la escena de superposición superior y actualmente activa.
  • STARTED: Se limita a STARTED para cualquier escena de superposición subyacente que esté cubierta por una superposición más reciente.

Estado del ciclo de vida a nivel de la entrada

La biblioteca administra el estado máximo del ciclo de vida de cada NavEntry individual en función de su presencia en la pila de actividades:

  • RESUMED: Si la entrada está presente en la pila de actividades actual, su ciclo de vida puede llegar hasta RESUMED (sujeto al límite a nivel de la escena).
  • CREATED: Si la entrada ya no está en la pila de actividades, por ejemplo, cuando se quitó, pero aún se renderiza en la pantalla mientras se anima, la biblioteca limita estrictamente su ciclo de vida a CREATED. Este límite garantiza que las entradas de segundo plano o de salida dejen de ejecutar el trabajo activo, como recopilar flujos o iniciar corrutinas vinculadas a estados RESUMED o STARTED mientras terminan sus transiciones de salida.

Cómo se combinan

Por ejemplo, el estado final del ciclo de vida de un NavEntry se resuelve de la siguiente manera:

Situación Límite a nivel de la escena Límite a nivel de la entrada Límite efectivo
Entrada activa, pantalla establecida (sin transiciones ni superposiciones) RESUMED RESUMED RESUMED
Entrada activa, durante la transición (navegación hacia o desde) STARTED RESUMED STARTED
Entrada activa, cubierta por una superposición (por ejemplo, se abre un diálogo) STARTED RESUMED STARTED
Entrada quitada, animación de salida STARTED o RESUMED CREATED CREATED

Revisión general

En el siguiente diagrama, se muestra cómo fluyen los datos entre los distintos objetos de Navigation 3:

Visualización de cómo fluyen los datos entre los distintos objetos en Navigation 3.
Figura 3. Diagrama que muestra cómo fluyen los datos a través de varios objetos en Navigation 3
  1. Los eventos de navegación inician cambios. Las claves se agregan o quitan de la pila de actividades en respuesta a las interacciones del usuario.

  2. El cambio en el estado de la pila de actividades activa la recuperación de contenido. El NavDisplay (un elemento componible que renderiza una pila de actividades) observa la pila de actividades. En su configuración predeterminada, muestra la entrada superior de la pila de actividades en un diseño de un solo panel. Cuando cambia la clave superior en la pila de actividades, el NavDisplay usa esta clave para solicitar el contenido correspondiente del proveedor de entradas.

  3. El proveedor de entradas proporciona contenido. El proveedor de entradas es una función que resuelve una clave en un NavEntry. Cuando recibe una clave del NavDisplay, el proveedor de entradas proporciona el NavEntry asociado, que contiene la clave y el contenido.

  4. Se muestra el contenido. El NavDisplay recibe el NavEntry y muestra el contenido.