Cómo guardar y administrar el estado de navegación

En las siguientes secciones, se describen estrategias para guardar tu pila de actividades y almacenar el estado asociado con las entradas en ella.

Guarda tu pila de actividades

Garantizar que el estado de navegación de tu app persista en varios eventos del ciclo de vida, incluidos los cambios de configuración y la finalización del proceso, es fundamental para una buena experiencia del usuario. En Navigation 3, eres propietario de tu pila de actividades, por lo que no hay lineamientos estrictos sobre cómo debes crearla o guardarla. Sin embargo, Navigation 3 ofrece un método de conveniencia que te proporciona una pila de actividades que se puede guardar: rememberNavBackStack.

Usa rememberNavBackStack

La función que admite composición rememberNavBackStack está diseñada para crear una pila de actividades que persista en los cambios de configuración y la finalización del proceso.

Para que rememberNavBackStack funcione correctamente, cada clave de tu pila de actividades debe cumplir con requisitos específicos:

  • Implementar la interfaz NavKey: Cada clave de la pila de actividades debe implementar la interfaz NavKey. Esta actúa como una interfaz de marcador que le indica a la biblioteca que la clave se puede guardar.
  • Tener la anotación @Serializable: Además de implementar NavKey, tus clases y objetos clave deben estar marcados con la anotación @Serializable.

En el siguiente fragmento, se muestra una implementación correcta de rememberNavBackStack:

@Serializable
data object Home : NavKey

@Composable
fun NavBackStack() {
    val backStack = rememberNavBackStack(Home)
}

Recuerda una pila de actividades con subtipos de NavKey

La función que admite composición rememberNavBackStack muestra un NavBackStack<NavKey>. Si tu app define su propio subtipo de NavKey del que heredan todas sus claves, puedes conservar esa escritura implementando una función de recuerdo personalizada, de la siguiente manera:

@Serializable
sealed interface MyAppNavKey : NavKey

@Serializable
data object ScreenA: MyAppNavKey

@Serializable
data class ScreenB(val id: String): MyAppNavKey

@Composable
fun rememberMyAppNavBackStack(vararg elements: MyAppNavKey): NavBackStack<MyAppNavKey> {
    return rememberSerializable(serializer = serializer()) {
        NavBackStack(*elements)
    }
}

@Composable
fun MyApp() {
    // defaultNavBackStack is NavBackStack<NavKey>
    val defaultNavBackStack = rememberNavBackStack(ScreenA)
    // myAppNavBackStack is NavBackStack<MyAppNavKey>
    val myAppNavBackStack = rememberMyAppNavBackStack(ScreenA)
}

Para obtener más ejemplos, incluido cómo controlar el polimorfismo abierto, consulta NavBackStackSamples.

Alternativa: Almacenamiento en un ViewModel

Otra forma de administrar tu pila de actividades es almacenarla en un ViewModel. Para la persistencia a través de la finalización del proceso cuando se usa un ViewModel o cualquier otro almacenamiento personalizado, debes hacer lo siguiente:

  • Asegúrate de que tus claves sean serializables: Al igual que con rememberNavBackStack, tus claves de navegación deben ser serializables.
  • Controla la serialización y deserialización de forma manual: Eres responsable de guardar manualmente la representación serializada de cada clave en el almacenamiento persistente (p.ej., SharedPreferences, una base de datos o un archivo) y deserializarla cuando tu app pasa a segundo plano o se restablece.

Cómo definir el alcance de ViewModels en NavEntrys

Los ViewModels se usan para retener el estado relacionado con la IU en los cambios de configuración, como las rotaciones de pantalla. De forma predeterminada, los ViewModels se definen en el ViewModelStoreOwner más cercano, que suele ser tu Activity o Fragment.

Sin embargo, es posible que desees definir el alcance de un ViewModel en un NavEntry específico (es decir, una pantalla o destino específico) en la pila de actividades, en lugar de toda la Activity. Esto garantiza que el estado de ViewModel se retenga solo mientras ese NavEntry en particular forme parte de la pila de actividades y se borre cuando se quite el NavEntry.

La biblioteca complementaria androidx.lifecycle:lifecycle-viewmodel-navigation3 proporciona un NavEntryDecorator que facilita esto. Este decorador proporciona un ViewModelStoreOwner para cada NavEntry. Cuando creas un ViewModel dentro del contenido de un NavEntry (p.ej., con viewModel() en Compose), se define automáticamente en la clave de ese NavEntry específico en la pila de actividades. Esto significa que el ViewModel se crea cuando se agrega el NavEntry a la pila de actividades y se borra cuando se quita.

Para usar NavEntryDecorator para definir el alcance de ViewModels en NavEntrys, sigue estos pasos:

  1. Agrega la dependencia androidx.lifecycle:lifecycle-viewmodel-navigation3 a tu archivo app/build.gradle.kts.
  2. Agrega el predeterminado rememberSaveableStateHolderNavEntryDecorator() a la lista de entryDecorators cuando construyas un NavDisplay.
  3. Agrega rememberViewModelStoreNavEntryDecorator() a la lista de entryDecorators.

NavDisplay(
    entryDecorators = listOf(
        // Add the default decorators for managing scenes and saving state
        rememberSaveableStateHolderNavEntryDecorator(),
        // Then add the view model store decorator
        rememberViewModelStoreNavEntryDecorator()
    ),
    backStack = backStack,
    entryProvider = entryProvider { },
)