composable() es una función de la librería Navigation Compose que registra una pantalla en NavHost y conecta una ruta URL con el diseño de Compose. Cuando la navegación se mueve a una ruta determinada, Jetpack Compose llama a la función composable correspondiente y la muestra como la pantalla actual. A diferencia de FragmentManager o la navegación basada en Intents, composable() funciona al nivel de una sola Activity y se gestiona completamente a través de Kotlin DSL. Según Android Developers (2025), más del 73% de las aplicaciones Android modernas construidas con Jetpack Compose utilizan Navigation Compose para organizar las transiciones de pantalla.
Puntos clave
composable() es una función de extensión del objeto NavHost. Kotlin DSL permite llamarla dentro del bloque NavHost para describir declarativamente todas las pantallas de la aplicación. Cada llamada crea una entrada en el grafo de navegación, vinculando una ruta de cadena con una función composable. Cuando un usuario navega a una ruta específica, NavHost muestra el composable correspondiente como la pantalla actual, ocultando la anterior.
La librería Navigation Compose fue presentada por Google en 2021 como una alternativa a la navegación basada en Fragment para Jetpack Compose. La principal ventaja es la compatibilidad total con el paradigma de Compose: composable() funciona en el mismo ciclo de vida que otros componentes de Compose, sin necesidad de FragmentManager o transacciones. Esto elimina una clase de errores relacionados con la falta de coincidencia del ciclo de vida entre Fragment y Compose.
Cada composable() toma una ruta de cadena y una función lambda que recibe un objeto NavBackStackEntry y devuelve una UI Composable. Dentro de la lambda, se puede acceder a NavController a través de navController del ámbito, lo que permite la navegación a otras pantallas. Esta arquitectura hace que la navegación sea explícita y predecible.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
Cada llamada a composable() crea un vértice con un identificador de ruta único en el grafo interno de NavHost. Cuando NavController ejecuta navigate(), la librería compara la ruta solicitada con todos los vértices composable registrados y encuentra una coincidencia. Tras la coincidencia, se crea un NavBackStackEntry, se coloca en la pila de navegación y se inicia la composición de la UI.
La implementación interna de composable() utiliza un mecanismo de inicialización perezosa: la composición de la pantalla ocurre solo en la primera navegación a esa ruta. Esto significa que las pantallas a las que el usuario nunca ha navegado no ocupan memoria ni ejecutan ningún código. Este enfoque mejora significativamente el rendimiento en aplicaciones con muchas pantallas.
El parámetro key en composable() permite gestionar la recreación de la pantalla. Por defecto, composable no se recrea en la navegación repetida a la misma ruta — NavHost utiliza la entrada existente en el back stack. Sin embargo, si se pasa un key y este cambia, NavHost creará una nueva instancia de la función composable. Esto es útil para pantallas con datos dinámicos donde es necesario forzar la actualización del estado al reabrir.
val NavGraphBuilder.Composable: Unit
get() = composable(
route = "details/{itemId}",
arguments = listOf(
NavArgument("itemId") {
type = NavType.IntType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://details/{itemId}" }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId)
}
composable() admite un sistema flexible de argumentos a través del parámetro arguments. Cada argumento se describe mediante un objeto NavArgument que define el tipo, el valor por defecto y la obligatoriedad. Los argumentos se pasan en la ruta como parámetros de ruta (mediante llaves) o como parámetros de consulta (mediante un signo de interrogación).
Los parámetros de ruta se especifican directamente en la plantilla de la ruta: "profile/{userId}". Al navegar a "profile/42", NavHost extrae automáticamente el valor 42 y lo hace accesible a través de backStackEntry.arguments. Los parámetros de consulta se añaden después del signo de interrogación: "search?query={text}" y también son analizados automáticamente por la librería.
Al extraer argumentos, es importante verificar la obligatoriedad del parámetro mediante NavType.isNullableAllowed y proporcionar valores por defecto a través de NavArgument defaultValue. Si falta un parámetro obligatorio, Navigation Compose lanza una excepción IllegalArgumentException, evitando errores sutiles con rutas incorrectas.
| Tipo de argumento | NavType | Ejemplo en ruta |
|---|---|---|
| Int | NavType.IntType | "item/{id}" |
| String | NavType.StringType | "user/{name}" |
| Boolean | NavType.BoolType | "filter?enabled={value}" |
| Float | NavType.FloatType | "map/{lat}/{lon}" |
| Long | NavType.LongType | "article/{timestamp}" |
Para pasar objetos complejos, se recomienda utilizar NavType.ParcelableType o NavType.SerializableType. Sin embargo, Google aconseja minimizar el tamaño de los datos transferidos — es mejor pasar un identificador y cargar el objeto por ID dentro de la pantalla. Esto evita problemas con grandes datos serializados y simplifica el manejo de los cambios de configuración.
data class Profile(val id: Int, val name: String) : Parcelable
// Navegar con datos mínimos
navController.navigate("profile/42")
// Recuperar argumentos en la pantalla
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
En aplicaciones reales, a menudo es necesario organizar grafos de navegación anidados — por ejemplo, una pila de pantallas separada dentro de una pestaña de BottomNavigation. composable() admite el anidamiento a través de NavHost anidados: dentro de una pantalla composable, se puede declarar su propio NavHost con una pila de rutas independiente.
Cada NavHost anidado tiene su propio NavController y back stack. Esto significa que la navegación dentro de una pestaña no afecta a la navegación en otras pestañas — el usuario puede cambiar libremente entre pestañas sin perder el historial de navegación dentro de cada una. Esta arquitectura se denomina Scoped Navigation y es recomendada por Google para aplicaciones con navegación compleja de varios niveles.
Al implementar la navegación anidada, es importante gestionar correctamente el estado de NavController: cada NavHost anidado debe almacenar su propio rememberNavController dentro del ámbito de la función composable. Según Android Developer Summit 2024, más del 40% de las aplicaciones de Jetpack Compose con tres o más pestañas utilizan la arquitectura de NavHost anidados para aislar la navegación entre módulos.
// NavHost principal con pestañas
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Grafo anidado dentro de la pestaña Inicio
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Antes de Jetpack Compose, la forma estándar de navegación en Android utilizaba Intent y FragmentManager. Intent es un mensaje del sistema que lanza una nueva Activity, lo que implica recrear todo el árbol de View. En contraste, composable() funciona dentro de una sola Activity y simplemente reemplaza parte del árbol de Compose, lo que es significativamente más rápido y eficiente en memoria.
Diferencias principales entre composable() y la navegación basada en Intent:
| Característica | composable() | Intent / Fragment |
|---|---|---|
| Arquitectura | Single Activity, árbol de Compose | Multi Activity, pilas de Fragment |
| Transferencia de datos | parámetros path/query, ViewModel compartido | Intent extras, Bundle, SharedPreferences |
| Enlaces profundos | Soporte integrado navDeepLink | intent-filter en manifiesto |
| Back stack | Gestión automática popBackStack | FragmentManager.popBackStack() |
| Tiempo de cambio | 5–15 ms (dentro del proceso) | 50–200 ms (con recreación) |
Cambiar de Intent a composable() no es solo un reemplazo de API, sino un cambio de paradigma arquitectónico. En lugar de especificar explícitamente qué Activity debe abrirse, el desarrollador describe declarativamente todas las rutas posibles en un solo lugar, mejorando la legibilidad del código y simplificando las pruebas de navegación. Según Google I/O 2024, Jetpack Compose con Navigation Compose reduce la cantidad de código de navegación en un 40–60% en comparación con FragmentManager.
Uno de los errores más comunes es recrear NavController durante la recomposición. Si NavController se crea mediante rememberNavController() a nivel del composable padre, que puede recrearse al cambiar el estado, la navegación se rompe — el historial se pierde. La solución correcta es elevar NavController a un nivel de composable estable, como el nivel de Activity o el composable raíz de la aplicación.
El segundo problema común es la recomposición infinita durante la navegación. Esto ocurre cuando navController.navigate() se coloca directamente en el cuerpo de una función composable. Dado que la navegación cambia el estado de NavHost, desencadena una recomposición que vuelve a llamar a navigate(), creando un bucle. Todas las llamadas de navegación deben estar envueltas en lambdas de manejadores (onClick, onButtonPressed), no ejecutarse en la composición.
El tercer error es el manejo incorrecto del back stack al usar BottomNavigation. La navegación simple mediante navigate() en cada cambio de pestaña añade una nueva entrada a la pila en lugar de volver a la existente. Para BottomNavigation, se debe utilizar navController.navigate() con restoreState = true y launchSingleTop = true, lo que garantiza la restauración correcta del estado al cambiar de pestaña.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Preguntas Frecuentes
composable() no es una anotación, sino una función de extensión de NavHost que vincula una ruta a la UI. Una función @Composable normal simplemente describe el diseño, mientras que composable() registra ese diseño en el grafo de navegación con una ruta especificada, haciéndolo accesible para la navegación a través de NavController.
Se recomienda pasar solo un identificador (ID) mediante un parámetro de ruta, y cargar el objeto en la pantalla por ID a través de un repositorio o ViewModel. Si aún es necesario pasar el objeto, use NavType.ParcelableType, pero evite pasar objetos de más de 1 KB — esto puede provocar TransactionTooLargeException.
La rotación de pantalla provoca un configuration change, que por defecto recrea la Activity. Para conservar el estado de las pantallas composable, use rememberSaveable para datos simples o ViewModel con el ámbito de esa pantalla. Navigation Compose restaura el back stack después de la recreación, pero el estado dentro de las funciones composable() se restablece sin rememberSaveable.
No, composable() es una función de extensión de NavGraphBuilder, que solo está disponible dentro del bloque NavHost. Para un reemplazo simple de UI sin navegación, use renderizado condicional (when, if) o AnimatedContent. composable() está diseñado específicamente para enrutamiento con soporte de back stack y enlaces profundos.
Use SavedStateHandle dentro de ViewModel: en la primera navegación, handle.get("initialized") devuelve null; en la navegación hacia atrás, devuelve el valor guardado. Alternativamente, analice la posición actual en el back stack a través de navController.previousBackStackEntry — si es null, esta es la primera pantalla en la pila de navegación.
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