Navigation Compose es una biblioteca Jetpack para navegación declarativa dentro de aplicaciones Android construidas con Jetpack Compose. En lugar de FragmentManager o navegación basada en Intent, Navigation Compose ofrece un único grafo de rutas gestionado a través de NavController y NavHost. Según Google I/O (2025), Navigation Compose es el método de navegación recomendado para aplicaciones Compose, utilizado en más del 70% de los nuevos proyectos. La biblioteca admite el paso de argumentos tipificados, enlaces profundos, animaciones de transición e integración con ViewModel a través de SavedStateHandle.
Puntos clave
Navigation Compose es una biblioteca del conjunto Jetpack que proporciona un marco de navegación para aplicaciones Compose. La biblioteca se basa en los mismos principios que el Componente de Navegación para el sistema View, pero está adaptada a la naturaleza declarativa de Compose: en lugar de FragmentTransaction se utilizan funciones composables, y el grafo de navegación se construye mediante Kotlin DSL.
La diferencia clave entre Navigation Compose y la navegación clásica es la ausencia de FragmentManager. Cada pantalla es una función composable que se renderiza en NavHost cuando la ruta coincide. La pila de retroceso almacena no un Fragment, sino un registro con ruta, argumentos y estado. Esto simplifica la arquitectura y elimina los conflictos de ciclo de vida típicos de la navegación basada en Fragment.
Según Google (2025), Navigation Compose ha pasado de experimental a estable y forma parte de Jetpack desde la versión 2.8.0. La biblioteca admite Material3, navegación con seguridad de tipos (a través de Kotlin Serialization), grafos anidados y modularización. La única limitación es que la biblioteca no admite la pila de retroceso múltiple para BottomNavigation sin configuración manual, aunque Google está trabajando en ello.
La arquitectura de Navigation Compose gira en torno a tres entidades: NavController (gestión de la pila), NavHost (contenedor del grafo) y NavDestination (ruta individual con composable). La interacción entre ellas es declarativa: el desarrollador describe las rutas y los argumentos, y la biblioteca maneja los estados de carga, conservación y restauración.
NavController es el elemento central de Navigation Compose, que gestiona la pila de navegación. Se crea mediante rememberNavController() y se pasa a NavHost. NavController almacena la pila de retroceso, el punto de entrada actual y admite acciones diferidas (deeplink después de la inicialización del grafo).
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile/{userId}") { backStackEntry ->
ProfileScreen(
userId = backStackEntry.arguments?.getString("userId") ?: ""
)
}
}
}
Métodos principales de NavController: navigate(route) — navegar a una ruta, popBackStack() — volver a la pantalla anterior, navigateAndClear(route) — navegar y limpiar la pila. NavOptions especifican el comportamiento: launchSingleTop evita duplicados, popUpTo limpia la pila hasta la ruta especificada, restoreState restaura el estado anterior.
Para acceder a NavController desde funciones composables profundamente anidadas, use NavHostController a través de CompositionLocal. LocalNavController se proporciona en ScopedNavController dentro de NavHost. Fuera de NavHost (por ejemplo, en BottomNavigation), el controlador se pasa a través de parámetros o ViewModel.
NavHost es un contenedor composable que vincula NavController con el grafo de rutas. Cada ruta se declara mediante composable(route, arguments, deepLinks), donde route es una cadena de ruta con marcadores de posición {param} opcionales. Cuando la ruta actual coincide, NavHost renderiza el bloque composable correspondiente.
El grafo de rutas se construye jerárquicamente: los grafos se pueden anidar mediante navigation() para agrupar rutas dentro de un módulo. Los grafos anidados tienen su propio startDestination y se combinan bajo un prefijo de ruta común. Esto permite organizar una arquitectura modular donde cada módulo de funcionalidad registra su propio subgrafo.
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
NavHost maneja automáticamente el botón de retroceso del sistema (back press) a través de LocalBackDispatcher. En Material3 Scaffold, por defecto recoge NavController para el correcto funcionamiento de BottomNavigation. NavHost recrea el composable cuando la ruta cambia, pero conserva el estado mediante rememberSaveable para campos de entrada y desplazamiento.
Navigation Compose admite el paso de argumentos tipificados entre pantallas a través de parámetros de ruta y NavType. Los parámetros se especifican en la ruta como {paramName} con el tipo indicado mediante arguments en composable(). NavType admite String, Int, Long, Float, Boolean, Parcelable y Serializable.
| Tipo de argumento | NavType | Ejemplo de ruta |
|---|---|---|
| String | NavType.StringType | "profile/{name}" |
| Int | NavType.IntType | "item/{id}" |
| Boolean | NavType.BoolType | "settings?enabled={flag}" |
| Parcelable | NavType.ParcelableType | "details/{item}" |
| Float | NavType.FloatType | "map?lat={lat}&lng={lng}" |
Los argumentos se extraen de NavBackStackEntry a través de arguments?.getType(key). Para parámetros obligatorios use defaultValue, para opcionales — nullable. El soporte de Parcelable funciona solo con Kotlin Parcelize o la biblioteca kotlinx.parcelize. Para objetos complejos, se recomienda pasar un ID y cargar datos a través de ViewModel, en lugar de serializar el objeto completo.
Desde Navigation 2.8.0, se introdujo la navegación con seguridad de tipos con Kotlin Serialization: las rutas se definen como clases de datos y los argumentos como campos. Esto reemplaza las rutas de cadena con objetos tipificados y elimina errores en los nombres de las rutas. La migración requiere el plugin de Kotlin Serialization y la dependencia navigation-compose-typesafe.
@Serializable
/* sealed class Route */
sealed class ProfileRoute(val route: String) {
data object Home : ProfileRoute("home")
data class Profile(val userId: String) : ProfileRoute("profile/{userId}")
}
Enlaces profundos es un mecanismo de navegación que permite abrir una pantalla específica de la aplicación a través de una URL o intent-filter. En Navigation Compose, los enlaces profundos se configuran mediante el parámetro deepLinks en composable() y se procesan automáticamente cuando la URI coincide con el patrón.
Un enlace profundo se especifica como una lista de UriPattern: "https://example.com/profile/{userId}". Los parámetros de URI se asignan automáticamente a los argumentos de la ruta. NavController procesa los enlaces profundos al iniciar la aplicación (a través de intent) y durante la operación (a través de enlaces profundos implícitos). Para manejar enlaces profundos pendientes, use handleDeepLink() en NavController después de la inicialización del grafo.
Según Google, se recomiendan enlaces profundos para: notificaciones push (Firebase Dynamic Links), verificación de correo electrónico, compartir contenido y navegación transversal desde enlaces web. Para Android 12+, se utilizan Digital Asset Links para verificar la autoridad del enlace profundo. AndroidManifest.xml debe contener un intent-filter con autoVerify="true" para abrir enlaces sin diálogo.
composable(
route = "profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType }),
deepLinks = listOf(
navDeepLink { uriPattern = "https://example.com/profile/{userId}" }
)
) { backStackEntry ->
ProfileScreen(userId = backStackEntry.arguments?.getString("userId") ?: "")
}
Limitaciones de los enlaces profundos en Navigation Compose: la biblioteca no admite enlaces profundos diferidos — un enlace profundo se procesa solo después de que NavHost haya construido completamente el grafo. Si un enlace profundo llega antes de la inicialización del grafo, debe diferirse mediante intent?.data y procesarse en LaunchedEffect. Para Firebase Dynamic Links, use el SDK de Firebase Dynamic Links junto con Navigation Compose.
Navigation Compose admite animaciones de transición mediante los parámetros enterTransition, exitTransition, popEnterTransition y popExitTransition en composable(). Las animaciones se implementan usando la API de animación de Compose: fadeIn, slideInHorizontally, expandIn y otras. Por defecto, la animación está desactivada — las pantallas se reemplazan instantáneamente.
Escenarios típicos de animación: slideInHorizontally para navegación hacia adelante (la pantalla entra desde la derecha), slideOutHorizontally para retroceder (la pantalla sale hacia la derecha). Para BottomNavigation, se usa más comúnmente la animación de desvanecimiento sin deslizamiento. Las animaciones se configuran a través de NavHost y se aplican a todos los composables a menos que se especifiquen individuales.
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally() + fadeIn() },
exitTransition = { slideOutHorizontally() + fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { slideOutHorizontally() + fadeOut() }
) { /* composable */ }
Las animaciones se pueden anular para cada composable individualmente pasando parámetros de animación directamente en composable(). Es importante que las animaciones no entren en conflicto con la animación de retroceso del sistema. Para la transición de elementos compartidos, se requiere la biblioteca accompanist-navigation-animation o una implementación personalizada mediante Modifier.graphicsLayer. Según Android Developers (2025), el 80% de las aplicaciones de producción utilizan animación de deslizamiento horizontal para la navegación estándar.
Preguntas frecuentes
Navigation Compose funciona sin Fragment, utilizando funciones composables y Kotlin DSL para el grafo. El Componente de Navegación (View) se basa en FragmentManager y grafos XML. La versión Compose es más simple, más rápida y no tiene ciclos de vida de Fragment. El Componente de Navegación para View solo es adecuado para aplicaciones híbridas.
Se recomienda pasar el ID del objeto y cargar los datos a través de ViewModel con SavedStateHandle. Si el objeto es simple, use Parcelable mediante kotlinx.parcelize. Pasar objetos grandes directamente a través de argumentos (Bundle) está limitado a ~1 MB y puede causar TransactionTooLargeException.
Sí, mediante la función navigation(route, startDestination) dentro de NavHost. Los grafos anidados tienen su propio startDestination y se combinan bajo un prefijo de ruta común. Esto permite organizar una arquitectura modular con grafos aislados para cada módulo de funcionalidad.
NavController maneja automáticamente la pulsación de retroceso a través de BackHandler de Compose. Llame a navController.popBackStack() cuando se presione Atrás. Para manejo personalizado (confirmación de salida), use BackHandler(enabled = condition) { callback } antes de llamar a popBackStack().
Actualmente, Navigation Compose no es compatible con Compose Multiplatform. Para la parte iOS de proyectos multiplataforma, use Voyager o Decompose. Google está trabajando en soporte para KMP, pero no hay fecha de lanzamiento. Para proyectos solo Android, Navigation Compose es la única opción recomendada.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también