Curso de Patrones MVVM en Android

Grafo de navegación en Jetpack Compose

Curso de Patrones MVVM en Android

Grafo de navegación en Jetpack Compose

Resumen

Construir un flujo de onboarding en una app Android requiere un sistema de navegación claro que conecte cada pantalla con la siguiente hasta llegar al home. Aquí aprendes a configurar rutas, armar un grafo de navegación con NavHost y enlazar tu MainActivity usando Jetpack Compose, todo aplicado a un proyecto real de tracker de calorías.

¿Cómo defino las rutas de navegación en Jetpack Compose?

Antes de pensar en pantallas, necesitas declarar las rutas que las identifican. Cada ruta es un objeto serializable que actúa como dirección única dentro de tu grafo.

En el flujo de onboarding del proyecto se declaran siete rutas principales más una de destino final:

  • heightScreenRoute para capturar la altura del usuario.
  • weightScreenRoute para el peso.
  • activityLevelScreenRoute para el nivel de actividad física.
  • goalScreenRoute para el objetivo nutricional.
  • nutrientGoalScreenRoute como cierre del onboarding.
  • trackerOverviewScreenRoute que funciona como home screen y punto de salida del módulo.

¿Qué es una ruta en Jetpack Compose? Es un identificador único, normalmente un objeto serializable, que el NavHost usa para saber qué composable renderizar en cada momento.

¿Cómo construyo el grafo de navegación con NavHost?

El grafo vive en un archivo Kotlin nuevo dentro de la carpeta navigation, llamado NavigationRoot. Esta función composable centraliza toda la navegación a nivel de proyecto.

¿Qué parámetros recibe NavigationRoot?

La función se anota con @Composable y recibe un NavHostController, una librería propia de Jetpack Compose que actúa literalmente como host de la navegación entre componentes. No necesita un Modifier porque no altera nada visual.

Dentro se coloca un Box como contenedor y, dentro de él, un NavHost que recibe dos cosas:

  1. El navController declarado como parámetro.
  2. Un startDestination, que define la pantalla inicial. En este caso, WelcomeScreenRoute.

¿Cómo encadeno las pantallas del onboarding?

Cada composable dentro del NavHost se asocia a su ruta y recibe una lambda que dispara la navegación a la siguiente. La cadena queda así, como una escalera en bajada:

  • WelcomeScreen navega a GenderScreen.
  • GenderScreen navega a AgeScreen.
  • AgeScreen navega a HeightScreen.
  • HeightScreen navega a WeightScreen.
  • WeightScreen navega a ActivityLevelScreen.
  • ActivityLevelScreen navega a GoalScreen.
  • GoalScreen navega a NutrientGoalScreen.
  • NutrientGoalScreen navega a TrackerOverviewScreen.

La última pantalla, TrackerOverviewScreen, se declara en una sola línea sin lambda de navegación, porque es el destino final del flujo y representa el home de la app.

Un detalle fácil de pasar por alto al copiar y pegar bloques: cada ruta debe coincidir con el composable correcto. Si copias WelcomeScreen cinco veces, terminas con un grafo que apunta siempre al mismo lugar. Revisa que HeightScreen llame a HeightScreen y no a WeightScreen.

¿Cómo conecto el grafo con MainActivity?

En la MainActivity original ya estaba declarada la WelcomeScreen directamente. Para usar el sistema de navegación tienes que reemplazar esa lógica.

El reemplazo se hace en dos pasos:

  1. Crear una variable navController igual a rememberNavController(), una función compose que conserva el controlador a través de recomposiciones.
  2. Renderizar NavigationRoot pasándole ese navController como parámetro navHostController.

Con eso, la MainActivity delega toda la navegación al grafo y arranca en WelcomeScreen.

¿Para qué sirve rememberNavController? Crea y recuerda una instancia de NavHostController durante el ciclo de vida del composable, evitando que se pierda al recomponer la pantalla.

¿Por qué mi navegación no funciona aunque el grafo esté completo?

Un error común al correr la app por primera vez es que el botón next no hace nada. El grafo está bien, las rutas están bien, pero no pasa nada al tocar el botón.

La causa suele estar en el ActionButton: la lambda onNextClick está definida pero no se invoca. Tienes que llamarla explícitamente desde el botón para que dispare la navegación. Lo mismo aplica a cada pantalla del flujo: hay que pasar la lambda onNextClick desde el composable padre hasta el ActionButton interno.

¿Qué hago si el botón next no navega? Verifica que la lambda onNextClick se esté invocando dentro del ActionButton y que el composable padre la reciba como parámetro y la propague.

Una vez agregada la invocación en WelcomeScreen y GenderScreen, el flujo avanza: de welcome a gender, de gender a age, y así sucesivamente. El resto de pantallas funciona igual: agregar la lambda, propagarla al botón y probar.

Ahora te toca completar las lambdas faltantes en cada pantalla del onboarding y verificar que llegas hasta TrackerOverviewScreen. ¿En qué pantalla se te rompió la navegación? Cuéntalo en los comentarios.