«Маршрут потерян»: почему Android Navigation Component сходит с ума
Вы когда-нибудь запускали своё приложение на Android, переходили на экран, нажимали «Назад» — и вдруг приложение падало с ошибкой IllegalArgumentException: navigation destination xxx is unknown to this NavController? Если да — вы не одиноки. Этот вопрос собрал тысячи просмотров на Stack Overflow, и каждый второй разработчик, начинающий работать с Jetpack Navigation, сталкивается с ним.
В этой статье мы разберём, почему возникает IllegalArgumentException navigation destination unknown, как его диагностировать и, главное, как исправить. Будет много живого кода на Kotlin, практических советов и никакой «воды». Поехали!
Что такое NavController и почему он «не знает» ваш пункт назначения?
Для начала — немного теории. NavController — это главный дирижёр навигации в Android. Он управляет переходами между экранами (fragments или composables) на основе графа навигации — XML-файла, где описаны все возможные маршруты. Когда вы вызываете navController.navigate(R.id.some_destination), NavController ищет этот ID в своём графе. Если не находит — выбрасывает IllegalArgumentException.
Звучит логично, но почему это происходит в рантайме, а не во время компиляции? Потому что граф навигации загружается динамически, и ошибка часто связана с тем, что текущий граф (который содержит NavController) просто не включает запрашиваемый пункт назначения.
Самые частые сценарии ошибки
- Попытка перейти на экран, который не описан в текущем графе — например, вы переключаете фрагменты, а NavController остался от предыдущего.
- Использование глубоких ссылок (DeepLink) без правильной регистрации — приложение запускается по ссылке, но граф ещё не загружен.
- Очистка back stack и попытка навигации после этого — NavController «забывает» свои маршруты.
- Неправильный ID пункта назначения — опечатка или несоответствие имени в коде и XML.
Разбор на реальном примере (Kotlin)
Представьте, у вас есть приложение с двумя экранами: HomeFragment и ProfileFragment. Граф навигации nav_graph.xml выглядит так:
<!-- nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.app.HomeFragment"
android:label="Home">
<action
android:id="@+id/action_home_to_profile"
app:destination="@id/profileFragment" />
</fragment>
<fragment
android:id="@+id/profileFragment"
android:name="com.example.app.ProfileFragment"
android:label="Profile" />
</navigation>
В HomeFragment вы делаете переход:
// HomeFragment.kt
val navController = findNavController()
navController.navigate(R.id.action_home_to_profile)
Всё работает. Но что, если вы попытаетесь вызвать navController.navigate(R.id.profileFragment) из другого контекста — например, из Activity или BroadcastReceiver? Или если вы перезагрузили граф? Тут и появляется IllegalArgumentException: navigation destination xxx is unknown to this NavController.
Воспроизводим ошибку
Допустим, вы добавили кнопку «Назад» в тулбар и написали такой код:
// MainActivity.kt
supportActionBar?.setDisplayHomeAsUpEnabled(true)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
// ОШИБКА: пытаемся перейти на homeFragment, но NavController может быть не готов
findNavController(R.id.nav_host_fragment).navigate(R.id.homeFragment)
true
}
else -> super.onOptionsItemSelected(item)
}
}
Если вы нажмёте «Назад» сразу после запуска приложения, пока граф ещё не инициализирован, вы получите IllegalArgumentException navigation destination unknown. NavController существует, но его граф пуст или не содержит homeFragment.
Как исправить? 5 проверенных решений
1. Всегда проверяйте текущий пункт назначения
Перед навигацией убедитесь, что вы не находитесь уже на целевом экране. Это предотвращает «лишние» переходы, которые могут сбить NavController.
val navController = findNavController()
val currentDestination = navController.currentDestination?.id
if (currentDestination != R.id.homeFragment) {
navController.navigate(R.id.homeFragment)
}
2. Используйте Safe Args для типизированной навигации
Safe Args генерирует классы направлений на этапе компиляции. Это исключает опечатки в ID и гарантирует, что маршрут существует в графе. Добавьте в build.gradle:
android {
...
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
// Не забудьте применить плагин в корневом build.gradle
}
Теперь переход выглядит так:
// HomeFragment.kt
val action = HomeFragmentDirections.actionHomeToProfile()
navController.navigate(action)
Если вы попытаетесь использовать несуществующий action, код просто не скомпилируется. Никаких IllegalArgumentException navigation destination unknown в рантайме!
3. Сбрасывайте граф при очистке back stack
Если вы очищаете back stack (например, после логина), NavController может «забыть» свои маршруты. Лучше использовать navigate() с флагами NavOptions:
val navOptions = NavOptions.Builder()
.setPopUpTo(R.id.homeFragment, inclusive = true) // очищаем стек до home
.setLaunchSingleTop(true) // не создаём дубликаты
.build()
navController.navigate(R.id.profileFragment, null, navOptions)
4. Инициализируйте NavController после загрузки графа
Если вы получаете NavController слишком рано (например, в onCreate Activity), дождитесь, пока NavHostFragment будет готов. Используйте addOnNavigatedListener или проверяйте currentDestination:
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
// Ждём, пока граф загрузится
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.homeFragment) {
// Теперь можно безопасно навигировать
}
}