Navigation y la pila de actividades

NavController contiene una "pila de actividades" con los destinos que visitó el usuario. A medida que el usuario navega a través de las pantallas de tu app, NavController agrega y quita destinos hacia y desde la pila de actividades.

Debido a su naturaleza, la pila de actividades es una estructura de datos del tipo "último en entrar, primero en salir". Por lo tanto, NavController le envía objetos y los muestra en la parte superior de la pila.

Comportamiento básico

A continuación, se enumeran los datos principales que debes considerar sobre el comportamiento de la pila de actividades:

  • Primer destino: Cuando el usuario abre la app, NavController envía el primer destino a la parte superior de la pila de actividades.
  • Envío a la pila: Cada llamada NavController.navigate() envía el destino determinado a la parte superior de la pila.
  • Destino principal emergente: Si presionas Up o Back, se llama a NavController.navigateUp() y NavController.popBackStack(), respectivamente. Se quita el destino superior de la pila. Consulta la página Principios de Navigation para obtener más información sobre la diferencia entre Up y Back.

Retroceso

El método NavController.popBackStack() intenta quitar el destino actual de la pila de actividades y navegar al destino anterior. De este modo, el usuario retrocede un paso en su historial de navegación. Muestra un valor booleano que indica si volvió a aparecer en el destino.

Retroceso a un destino específico

También puedes usar popBackStack() para navegar a un destino específico. Para hacerlo, usa una de sus sobrecargas. Hay varios que te permiten pasar un identificador, como un id entero o una cadena route. Estas sobrecargas llevan al usuario al destino asociado con el identificador determinado. Lo más importante es que muestran todo en la pila encima de ese destino.

Estas sobrecargas también toman un valor booleano inclusive. Determina si NavController también debe quitar el destino especificado de la pila de actividades después de haber navegado hacia él.

Observa este breve fragmento de ejemplo:

navController.popBackStack(R.id.destinationId, true)

Aquí, NavController vuelve al destino con el ID de número entero destinationId. Como el valor del argumento inclusive es true, NavController también muestra el destino determinado de la pila de actividades.

Qué hacer ante un retroceso fallido

Cuando popBackStack() muestra false, una llamada posterior a NavController.getCurrentDestination() muestra el valor null. Esto significa que la app quitó el último destino de la pila de actividades. En ese caso, el usuario solo ve una pantalla en blanco.

Esto puede suceder en los siguientes casos:

  • popBackStack() no mostró ningún elemento de la pila.
  • popBackStack() quitó un destino de la pila de actividades, y la pila ahora está vacía.

Para resolverlo, debes navegar a un destino nuevo o llamar a finish() en tu actividad para finalizarlo. Esto se demuestra en el siguiente fragmento:

Kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

Java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

Navegación a un destino

Para quitar destinos de la pila de actividades cuando navegas de un destino a otro, agrega un argumento popUpTo() a la llamada de la función navigate() asociada. popUpTo() indica a la biblioteca de Navigation que quite algunos destinos de la pila de actividades como parte de la llamada a navigate(). El valor del parámetro es el identificador de un destino en la pila de actividades. El identificador puede ser un número entero id o una cadena route.

Puedes incluir un argumento para el parámetro inclusive con un valor de true para indicar que el destino que especificaste en popUpTo() también debería salir de la pila de actividades.

Para implementar esto de manera programática, pasa popUpTo() a navigate() como parte de NavOptions con inclusive configurado como true. Esto funciona tanto en Compose como en View.

Guardado de un estado de apertura

Cuando usas popUpTo para navegar a un destino, tienes la opción de guardar los estados de todos los destinos que se quitaron de la pila de actividades.

Para habilitar esta opción, define popUpToSaveState como true en el objeto action asociado o llama a NavController.navigate().

Cuando navegas a un destino, también puedes definir restoreSaveState como true para restablecer automáticamente el estado asociado con el destino en la propiedad destination.

Ejemplo de XML

Aquí tienes un ejemplo de popUpTo en XML con una acción:

<action
  android:id="@+id/action_a_to_b"
  app:destination="@id/b"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:restoreState=”true”
  app:popUpToSaveState="true"/>

Ejemplos de Compose

El siguiente es un ejemplo completo de lo mismo en Compose:

@Composable
fun MyAppNavHost(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
    startDestination: String = "destination_a"
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        composable("destination_a") {
            DestinationA(
                onNavigateToB = {
                // Pop everything up to the "destination_a" destination off the back stack before
                // navigating to the "destination_b" destination
                    navController.navigate("destination_b") {
                        popUpTo("destination_a") {
                            inclusive = true
                            saveState = true
                        }
                    }
                },
            )
        }
        composable("destination_b") { DestinationB(/* ... */) }
    }
}

@ Composable
fun DestinationA(onNavigateToB: () -> Unit) {
    Button(onClick = onNavigateToB) {
        Text("Go to A")
    }
}

De forma más detallada, puedes cambiar el modo en que llamas a NavController.navigate(), como se muestra a continuación:

// Pop everything up to the destination_a destination off the back stack before
// navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a")
}

// Pop everything up to and including the "destination_a" destination off
// the back stack before navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a") { inclusive = true }
}

// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
    launchSingleTop = true
}

Si deseas obtener información general para pasar opciones a NavController.navigate(), consulta la guía de Navegación con opciones.

Navegación con acciones

Cuando navegas con una acción, puedes elegir quitar destinos adicionales de la pila de actividades. Por ejemplo, si tu app tiene un flujo de acceso inicial, una vez que un usuario accede, debes quitar todos los destinos relacionados con el acceso en la pila de actividades para que el botón Atrás no vuelva a dirigir a los usuarios al flujo de acceso.

Lectura adicional

Para obtener más información, consulta las siguientes páginas:

  • Navegación circular: Obtén información para evitar una pila de actividades sobrecargada en casos en los que los flujos de navegación sean circulares.
  • Destinos de diálogo: Obtén información sobre el modo en que los destinos de diálogo introducen consideraciones únicas para administrar la pila de actividades.