Cómo migrar Jetpack Navigation a Navigation Compose

La API de Navigation Compose te permite navegar entre elementos componibles en una app de Compose y aprovechar el componente, la infraestructura y las funciones de Jetpack Navigation.

En esta página, se describe cómo migrar de una Navigation de Jetpack basada en fragmentos a Navigation Compose, como parte de la migración más grande 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 con elementos componibles de pantalla correspondientes. Los elementos de pantalla componibles pueden contener una combinación de contenido de Compose y View, pero todos los destinos de navegación deben ser componibles para habilitar la migración de Navigation Compose. Hasta entonces, debes seguir usando el componente de Navigation basado en fragmentos en tu vista de interoperabilidad y la base de código de Compose. Consulta la documentación sobre la interoperabilidad de navegación para obtener más información.

Usar Navigation Compose en una app solo de Compose no es un requisito previo. Puedes seguir usando el componente de Navigation basado en fragmentos, siempre que conserves los fragmentos para alojar tu contenido componible.

Pasos de la migración

Ya sea que sigas nuestra estrategia de migración recomendada o estés tomando otro enfoque, llegarás a un punto en el que todos los destinos de navegación serán elementos componibles en la pantalla, y los fragmentos actuarán solo como contenedores componibles. 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, además de la capa de IU.

Para migrar a Navigation Compose, sigue estos pasos:

  1. Agrega la dependencia de Navigation Compose a tu app.
  2. Crea un elemento App-level componible y agrégalo a tu Activity como punto de entrada de Compose para reemplazar 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. Configura NavController en un lugar donde todos los elementos componibles que necesitan hacer referencia a él tengan acceso a él (por lo general, se encuentra dentro del elemento App componible). Este enfoque sigue los principios de elevación de estado y te permite usar NavController como fuente de confianza para navegar entre pantallas componibles y mantener la pila de actividades:

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

  4. Crea el elemento NavHost de tu app dentro del elemento componible de la app y pasa el navController:

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

  5. Agrega los destinos de composable para compilar tu gráfico de navegación. Si cada pantalla se migró anteriormente a Compose, este paso solo consiste en extraer estos elementos componibles de pantalla desde tus fragmentos hasta los destinos 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(/* ... */)
            }
            // ...
        }
    }

  6. Si seguiste las instrucciones para ar la arquitectura de la IU de Compose, específicamente cómo se deben pasar los elementos 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 pantalla componible. 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 = {},
    ) {
        // ...
    }

  7. Reemplaza todas las llamadas de navegación de findNavController() por los navController y pásalas como eventos de navegación a cada pantalla componible, en lugar de pasar el navController completo. Este enfoque sigue las prácticas recomendadas de exponer eventos de funciones de componibilidad a emisores y mantiene navController como la única fuente de confianza.

    1. Si ya usaste el complemento Safe Args para generar instrucciones y acciones de navegación, reemplázalo por una ruta, es decir, una ruta de acceso de string al elemento componible que es única para cada destino.
    2. Para reemplazar Safe Args cuando pasas datos, consulta Cómo navegar con argumentos.
    3. Para obtener seguridad de tipos en Navigation Compose, lee la sección Safe Args que se encuentra a continuación.

      @Composable
      fun SampleNavHost(
          navController: NavHostController
      ) {
          NavHost(navController = navController, startDestination = "first") {
              composable("first") {
                  FirstScreen(
                      onButtonClick = {
                          // findNavController().navigate(firstScreenToSecondScreenAction)
                          navController.navigate("second_screen_route")
                      }
                  )
              }
              composable("second") {
                  SecondScreen(
                      onIconClick = {
                          // findNavController().navigate(secondScreenToThirdScreenAction)
                          navController.navigate("third_screen_route")
                      }
                  )
              }
              // ...
          }
      }

  8. Quita todos los fragmentos, los diseños XML relevantes, la navegación innecesaria y otros recursos, y las dependencias de Navigation de Jetpack y Fragment inactivos.

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

Casos de uso comunes

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

Estos son algunos casos de uso comunes de la migración:

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

Safe Args

A diferencia de Jetpack Navigation, Navigation Compose no admite el uso del complemento Safe Args para la generación de código. En su lugar, puedes lograr la seguridad de tipos con Navigation Compose mediante la estructuración del código para que sea seguro de tipos en el tiempo de ejecución.

Recupera datos complejos durante la navegación

Navigation Compose se basa en rutas de cadenas y, a diferencia de Jetpack Navigation, no admite el paso de objetos Parcelables y Serializables personalizados como argumentos.

Te recomendamos que no pases objetos de datos complejos durante la navegación. En su lugar, pasa la información mínima necesaria, por ejemplo, un identificador único o alguna otra forma de ID, como argumentos cuando realices acciones de navegación. Debes almacenar objetos complejos como datos en una sola fuente de información, como la capa de datos. Si deseas obtener más información, consulta Cómo recuperar datos complejos cuando navegas.

Si tus fragmentos pasan objetos complejos como argumentos, considera refactorizar el código primero de una manera que permita almacenar y recuperar estos objetos desde 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 si 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, antes de AnimatedNavHost, ahora es directamente compatible con 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 cómo migrar 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 Now in Android: Es una app para Android completamente funcional que se compiló por completo con Kotlin y Jetpack Compose, y que sigue las prácticas recomendadas de diseño y desarrollo de Android, y también incluye Navigation Compose.
  • Cómo migrar Sunflower a Jetpack Compose: Entrada de blog en la que se 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 for every screen: 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.