«Ruta perdida»: por qué Android Navigation Component se vuelve loco
\n\n¿Alguna vez has iniciado tu aplicación en Android, has ido a una pantalla, has pulsado «Atrás» — y de repente la aplicación se ha bloqueado con el error IllegalArgumentException: navigation destination xxx is unknown to this NavController? Si es así, no estás solo. Esta pregunta ha acumulado miles de visitas en Stack Overflow, y uno de cada dos desarrolladores que empiezan a trabajar con Jetpack Navigation se encuentra con ella.
En este artículo, analizaremos por qué ocurre IllegalArgumentException navigation destination unknown, cómo diagnosticarlo y, lo más importante, cómo solucionarlo. Habrá mucho código vivo en Kotlin, consejos prácticos y nada de «relleno». ¡Allá vamos!
\n\n¿Qué es NavController y por qué «no conoce» tu destino?
\n\nPara empezar, un poco de teoría. NavController es el director principal de la navegación en Android. Gestiona las transiciones entre pantallas (fragments o composables) basándose en el grafo de navegación — un archivo XML donde se describen todas las rutas posibles. Cuando llamas a navController.navigate(R.id.some_destination), NavController busca ese ID en su grafo. Si no lo encuentra, lanza una IllegalArgumentException.
Suena lógico, pero ¿por qué ocurre esto en tiempo de ejecución y no durante la compilación? Porque el grafo de navegación se carga dinámicamente, y el error a menudo está relacionado con que el grafo actual (que contiene el NavController) simplemente no incluye el destino solicitado.
\n\nLos escenarios más comunes del error
\n- \n
- Intento de navegar a una pantalla que no está descrita en el grafo actual — por ejemplo, cambias de fragments, pero el NavController sigue siendo el anterior. \n
- Uso de enlaces profundos (DeepLink) sin el registro correcto — la aplicación se inicia mediante un enlace, pero el grafo aún no se ha cargado. \n
- Limpieza de la pila de retroceso (back stack) e intento de navegación después — NavController «olvida» sus rutas. \n
- ID de destino incorrecto — un error tipográfico o una discrepancia entre el nombre en el código y en el XML. \n
Análisis con un ejemplo real (Kotlin)
\n\nImagina que tienes una aplicación con dos pantallas: HomeFragment y ProfileFragment. El grafo de navegación nav_graph.xml se ve así:
<!-- nav_graph.xml -->\n<navigation xmlns:android="http://schemas.android.com/apk/res/android"\n xmlns:app="http://schemas.android.com/apk/res-auto"\n android:id="@+id/nav_graph"\n app:startDestination="@id/homeFragment">\n\n <fragment\n android:id="@+id/homeFragment"\n android:name="com.example.app.HomeFragment"\n android:label="Home">\n <action\n android:id="@+id/action_home_to_profile"\n app:destination="@id/profileFragment" />\n </fragment>\n\n <fragment\n android:id="@+id/profileFragment"\n android:name="com.example.app.ProfileFragment"\n android:label="Profile" />\n</navigation>\n\nEn HomeFragment haces la transición:
// HomeFragment.kt\nval navController = findNavController()\nnavController.navigate(R.id.action_home_to_profile)\n\nTodo funciona. Pero, ¿qué pasa si intentas llamar a navController.navigate(R.id.profileFragment) desde otro contexto — por ejemplo, desde Activity o BroadcastReceiver? ¿O si recargas el grafo? Ahí es donde aparece IllegalArgumentException: navigation destination xxx is unknown to this NavController.
Reproduciendo el error
\n\nSupongamos que has añadido un botón «Atrás» en la barra de herramientas y has escrito este código:
\n\n// MainActivity.kt\nsupportActionBar?.setDisplayHomeAsUpEnabled(true)\n\noverride fun onOptionsItemSelected(item: MenuItem): Boolean