Cómo migrar a Navigation 3

Navigation 3 representa un cambio fundamental en la forma en que Jetpack Compose controla el estado de navegación y ofrece ventajas arquitectónicas significativas en comparación con Navigation 2.

Comprende los cambios arquitectónicos y los pasos necesarios para migrar una app de Wear Compose de Navigation 2 a Navigation 3.

Ventajas clave de Navigation 3

  • Control directo de la pila de actividades: NavBackStack es, fundamentalmente, una lista mutable de objetos NavKey que representa el historial de pantallas que visitó el usuario. Puedes controlarla exactamente como lo harías con cualquier MutableList de Kotlin (add, removeLast, clear). Manipulas directamente la lista para realizar acciones de navegación, como agregar una clave para avanzar o quitar una clave para retroceder.
  • Diseño Compose-First: La pila de actividades se modela como un estado observable estándar La modificación del historial de navegación se comporta exactamente como la actualización de cualquier otro estado de Compose, lo que activa automáticamente la recomposición para mostrar la pantalla actual.
  • Tipo seguro de forma predeterminada: Se eliminan por completo las rutas basadas en cadenas. Navigation utiliza objetos de datos y clases de datos serializables.
  • Presentaciones desacopladas (estrategias de escena): La capa de transición de la IU (NavDisplay y SwipeDismissableSceneStrategy) está completamente separada del seguimiento de estado (NavBackStack), lo que permite una integración más simple de las transiciones de navegación integradas de Wear OS.

Pasos de la migración

1. Actualiza las dependencias

Quita la dependencia androidx.wear.compose:compose-navigation anterior y presenta las nuevas dependencias divididas de Navigation 3, junto con la compatibilidad con la serialización de Kotlin.

Quitar:

implementation("androidx.wear.compose:compose-navigation:...")

Agregar:

implementation("androidx.navigation3:navigation3-runtime:...") // State logic
implementation("androidx.navigation3:navigation3-ui:...")      // Display logic
implementation("androidx.wear.compose:compose-navigation3:...") // Wear gestures
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:...") // Requires compiler plugin

2. Actualiza los destinos para implementar NavKey

En Navigation 2, es posible que hayas usado cadenas o objetos genéricos para el enrutamiento. En Navigation 3, debes implementar la interfaz de marcador NavKey y anotar cada objeto de pantalla con @Serializable.

¿Por qué es necesario? Para garantizar que la pila de actividades se pueda guardar y restablecer en el cierre del proceso, el navigation3-runtime subyacente se basa en kotlinx-serialization para serializar el estado.

Antes (Navigation 2: Rutas genéricas de tipo seguro):

sealed class Nav2Screen {
    data object Landing : Nav2Screen()
    data object List : Nav2Screen()
}

Después (Navigation 3: NavKey + Serializable):

@Serializable
sealed interface MigrationScreen : NavKey {
    @Serializable
    data object Landing : MigrationScreen

    @Serializable
    data object List : MigrationScreen
}

3. Reemplaza la lógica de enrutamiento (NavController a NavBackStack)

Reemplaza tu NavController por un NavBackStack inicializado a través de rememberNavBackStack. También debes crear una instancia de SwipeDismissableSceneStrategy específicamente para Wear OS.

Antes (Navigation 2):

val navController = rememberSwipeDismissableNavController()

Después (Navigation 3):

val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey)
val strategy = rememberSwipeDismissableSceneStrategy<NavKey>()

4. Reemplaza NavHost por NavDisplay y el DSL entryProvider

El contenedor NavHost y su compilador composable("route") { ... } interno DSL se reemplazan por NavDisplay y el entryProvider { entry<Key> { ... } } DSL.

Antes (Navigation 2):

SwipeDismissableNavHost(navController = navController, startDestination = "menu") {
    composable("menu") {
        GreetingScreen(
            onShowList = { navController.navigate("list") }
        )
    }
    composable("list") {
        ListScreen()
    }
}

Después (Navigation 3):

NavDisplay(
    backStack = backStack,
    sceneStrategies = listOf(strategy),
    entryProvider = entryProvider {
        entry<MigrationScreen.Landing> {
            GreetingScreen(
                onShowList = { backStack.add(MigrationScreen.List) }
            )
        }
        entry<MigrationScreen.List> {
            ListScreen()
        }
    }
)