Cómo migrar Jetpack Navigation a Navigation Compose

La API de Navigation Compose te permite navegar entre elementos que admiten composición en una app de Compose, a la vez que aprovechas el componente, la infraestructura y las funciones de Jetpack Navigation.

En esta página, se describe cómo migrar de Jetpack Navigation basado en fragmentos a Navigation Compose, como parte de la migración de la IU basada en objetos View a Jetpack Compose.

Requisitos previos para la migración

Puedes migrar a Navigation Compose una vez que puedas reemplazar todos tus fragmentos por elementos de pantalla componibles correspondientes. Los elementos de componibilidad de la pantalla pueden contener una combinación de contenido de Compose y View, pero todos los destinos de navegación deben ser elementos de componibilidad para habilitar la migración de Navigation Compose. Hasta entonces, debes seguir usando el componente Navigation basado en fragmentos en tu base de código de interoperabilidad de View y Compose. Consulta la documentación de interoperabilidad de navegación para obtener más información.

Pasos de la migración

Ya sea que sigas nuestra estrategia de migración recomendada o adoptes otro enfoque, llegará un punto en el que todos los destinos de navegación serán elementos de pantalla componibles, y los fragmentos solo actuarán como contenedores de esos elementos. En esta etapa, puedes migrar a Navigation Compose.

Si tu app ya sigue un patrón de diseño de UDF y nuestra guía de arquitectura, la migración a Jetpack Compose y Navigation Compose no debería requerir refactorizaciones importantes de otras capas de tu app, aparte de la capa de la IU.

Para migrar a Navigation Compose, sigue estos pasos:

  1. Agrega la dependencia Navigation Compose a tu app.
  2. Crea un elemento App-level componible y agrégalo a tu Activity como punto de entrada de Compose, reemplazando la configuración del diseño de View:

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. Crea tipos para cada destino de navegación. Usa un data object para los destinos que no requieren datos y data class o class para los destinos que sí los requieren.

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. Configura el NavController en un lugar donde todos los elementos componibles que necesiten hacer referencia a él tengan acceso (por lo general, dentro de tu elemento componible App). Este enfoque sigue los principios de la elevación de estado y te permite usar el NavController como la fuente de confianza para navegar entre pantallas componibles y mantener la pila de actividades:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. Crea el NavHost de tu app dentro del elemento de componibilidad App y pasa el navController:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            // ...
        }
    }

  6. Agrega los destinos de composable para compilar tu gráfico de navegación. Si cada pantalla se migró previamente a Compose, este paso solo consiste en extraer estos elementos componibles de pantalla de tus fragmentos a los destinos de composable:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable<Second> {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  7. Si seguiste la guía sobre cómo diseñar la IU de Compose, específicamente cómo se deben pasar los ViewModel y los eventos de navegación a los elementos componibles, el siguiente paso es cambiar la forma en que proporcionas el ViewModel a cada elemento componible de pantalla. A menudo, puedes usar la inserción de Hilt y su punto de integración con Compose y Navigation a través de hiltViewModel:

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  8. Reemplaza todas las llamadas de navegación findNavController() por las de navController y pásalas como eventos de navegación a cada pantalla que admite composición, en lugar de pasar todo el navController. Este enfoque sigue las prácticas recomendadas para exponer eventos de funciones de componibilidad a los llamadores y mantiene el navController como la fuente única de información.

    Los datos se pueden pasar a un destino creando una instancia de la clase de ruta definida para ese destino. Luego, se puede obtener directamente de la entrada de la pila de actividades en el destino o de un ViewModel con SavedStateHandle.toRoute().

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. Quita todos los fragmentos, los diseños XML relevantes, la navegación innecesaria y otros recursos, así como las dependencias obsoletas de Fragment y Jetpack Navigation.

Puedes encontrar los mismos pasos con más detalles relacionados con Navigation Compose en la documentación de configuración.

Casos de uso habituales

Independientemente del componente de Navigation que uses, se aplican los mismos principios de navegación.

Entre los casos de uso comunes cuando se migra, se incluyen los siguientes:

Para obtener información más detallada sobre estos casos de uso, consulta Cómo navegar con Compose.

Cómo recuperar datos complejos durante la navegación

Te recomendamos que no pases objetos de datos complejos cuando navegues. En su lugar, pasa la información mínima necesaria, como un identificador único o alguna otra forma de ID, como argumentos, cuando se realizan acciones de navegación. Debes almacenar los objetos complejos como datos en una fuente única de información, como la capa de datos. Para obtener más información, consulta Cómo recuperar datos complejos durante la navegación.

Si tus fragmentos pasan objetos complejos como argumentos, considera primero refactorizar tu código de manera que permita almacenar y recuperar estos objetos de la capa de datos. Consulta el repositorio de Now in Android para ver ejemplos.

Limitaciones

En esta sección, se describen las limitaciones actuales de Navigation Compose.

Migración incremental a Navigation Compose

Actualmente, no puedes usar Navigation Compose mientras sigues usando fragmentos como destinos en tu código. Para comenzar a usar Navigation Compose, todos tus destinos deben ser elementos componibles. Puedes hacer un seguimiento de esta solicitud de función en la Herramienta de seguimiento de errores.

Animaciones de transición

A partir de Navigation 2.7.0-alpha01, la compatibilidad para configurar transiciones personalizadas, que antes se realizaba desde AnimatedNavHost, ahora se admite directamente en NavHost. Lee las notas de la versión para obtener más información.

Más información

Para obtener más información sobre la migración a Navigation Compose, consulta los siguientes recursos:

  • Codelab de Navigation Compose: Aprende los conceptos básicos de Navigation Compose con un codelab práctico.
  • Repositorio de Now in Android: Es una app para Android completamente funcional que se compiló por completo con Kotlin y Jetpack Compose, que sigue las prácticas recomendadas de diseño y desarrollo de Android, y que incluye Navigation Compose.
  • Cómo migrar Sunflower a Jetpack Compose: Es una entrada de blog que documenta el recorrido de migración de la app de ejemplo de Sunflower de Views a Compose, que también incluye la migración a Navigation Compose.
  • Jetnews para todas las pantallas: Es una entrada de blog que documenta la refactorización y la migración de la muestra de Jetnews para admitir todas las pantallas con Jetpack Compose y Navigation Compose.