NavController — это центральный компонент библиотеки Navigation Compose, управляющий стеком навигации и состоянием back stack в Android-приложениях. Через NavController выполняются переходы между экранами, возврат на предыдущие страницы и передача данных между маршрутами. По данным Android Developers (2025), NavController является обязательным элементом любого Compose-приложения с более чем одним экраном. Контроллер создаётся через rememberNavController(), передаётся в NavHost и доступен для вызова navigate() из любой точки композиции. Встроенная поддержка SavedStateHandle автоматически сохраняет состояние ViewModel при реконфигурации.
Главное
NavController — это класс из библиотеки Navigation Compose, реализующий навигационный контроллер для Compose-приложений. NavController управляет стеком NavBackStackEntry, где каждая запись содержит маршрут, аргументы и состояние экрана. Контроллер поддерживает базовые операции навигации: переход, возврат, замена и очистка.
В отличие от View-системы, где навигация шла через FragmentManager или Intent, NavController работает исключительно в Compose-контексте. Back stack хранится в виде графа NavDestination, а не Fragment-стопки. Это исключает overhead на создание и уничтожение Fragment, а также упрощает тестирование — NavController можно замокать через TestNavHostController.
NavController тесно связан с NavHost — контейнером, который рендерит текущий экран из графа. Без NavHost NavController не может отображать composable-функции, но сохраняет способность управлять стеком. В типовой архитектуре NavController создаётся на уровне Activity или главного composable и передаётся вниз по дереву композиции через параметры.
По данным Google, NavController прошёл несколько major-релизов. Версия 2.8.0 добавила Type-Safe Navigation, версия 2.9.0 — поддержку predictive back gesture (Android 14+). Контроллер совместим с Material3 Scaffold и BottomNavigation. Для мультимодульных проектов NavController передаётся через DI (Hilt/Koin) или параметры конструктора.
NavController создаётся через composable-функцию rememberNavController(). Функция возвращает экземпляр NavHostController (наследник NavController), привязанный к жизненному циклу текущего composable. При выходе из композиции контроллер очищается. Для сохранения контроллера при реконфигурации используйте rememberSaveable или ViewModel.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
Конфигурация NavController включает: NavHostController (основной), TestNavHostController (тестирование) и ScopedNavController (дочерний для вложенных графов). Для BottomNavigation NavController должен быть единым на всё приложение — создание нового контроллера в каждой вкладке приведёт к потере стека. Для передачи контроллера во вложенные экраны используйте параметр функции, а не CompositionLocalProvider, чтобы сохранить читаемость.
Для тестирования навигации используйте TestNavHostController с compose-test-rule. Контроллер позволяет задать начальный маршрут и проверить, что navigate() вызвал ожидаемый переход. Тестирование NavController не требует эмулятора — работает с Semantics-матчерами Compose Test.
Метод navigate(route: String) — основной способ навигации в NavController. Принимает строку маршрута, опциональные NavOptions и Navigator.Extras. NavOptions управляют поведением перехода: launchSingleTop (не дублировать маршрут в стеке), popUpTo (очистить стек до маршрута), restoreState (восстановить предыдущее состояние).
NavOptions задаются через builder-синтаксис: NavOptionsBuilder. Основные параметры: popUpTo (route + inclusive/saveState), launchSingleTop (Boolean, true — не создавать дубликат), restoreState (восстановить состояние при возврате). Без popUpTo каждый navigate() добавляет entry в стек, что приводит к накоплению back stack и некорректному поведению кнопки Back.
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras позволяет передать дополнительные данные, не входящие в маршрут: shared element для анимации, флаги Intent, Pac-Man bundle. Extras используются редко — в основном для интеграции с Accompanist Animation или кастомными Navigator. Для большинства сценариев достаточно строки маршрута и NavOptions.
popBackStack() — метод для возврата на предыдущий экран. Без аргументов удаляет верхнюю запись стека и возвращает true, если удаление прошло успешно. Если стек пуст — метод возвращает false, а Activity закрывается (аналогично super.onBackPressed()).
Перегруженная версия popBackStack(route: String, inclusive: Boolean) удаляет все записи до указанного маршрута. Если inclusive = true — удаляется и сам указанный маршрут. Метод возвращает Boolean — true, если удалось найти и удалить записи. Версия с inclusive полезна для сценариев «выход на корневой экран» после авторизации или оформления заказа.
| Метод | Описание | Пример |
|---|---|---|
| popBackStack() | Возврат на один экран назад | navController.popBackStack() |
| popBackStack(route, false) | Очистка до route (route остаётся) | popBackStack("home", false) |
| popBackStack(route, true) | Очистка до и включая route | popBackStack("home", true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | Переход с полной очисткой | navigate("login") { popUpTo(0) { inclusive = true } } |
Для обработки системной кнопки Back (hardware back button) используйте BackHandler из Compose. BackHandler принимает enabled и onBack — callback, вызываемый при нажатии. Для Android 14+ используется PredictiveBackGesture, интегрируемый через NavController с версии 2.9.0. Predictive back добавляет анимацию предварительного просмотра возврата.
SavedStateHandle — это механизм сохранения состояния ViewModel при навигации и реконфигурации. NavController автоматически предоставляет SavedStateHandle для каждой NavBackStackEntry. Через SavedStateHandle ViewModel хранит состояние экрана и восстанавливает его при возврате (restoreState = true).
В Navigation Compose SavedStateHandle используется совместно с ViewModel: ViewModel инициализируется через SavedStateHandle, который передаётся из backStackEntry. При переходе на другой экран и возврате (с restoreState) ViewModel получает сохранённое состояние, а не создаётся заново. Это критично для экранов с вводом данных, фильтрами или скроллом.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle поддерживает примитивные типы, String, Bundle и Parcelable. Для сложных объектов сохраняйте только ID, а полные данные загружайте из репозитория. Лимит SavedStateHandle — около 1 MB, превышение вызывает TransactionTooLargeException. Для больших объёмов используйте Room или DataStore вместо сохранения в handle.
Важно: SavedStateHandle сохраняет состояние только при использовании restoreState = true в NavOptions. Если restoreState не указан, при возврате ViewModel создаётся заново с дефолтными значениями. Для переключения BottomNavigation с restoreState NavController сохраняет состояние каждой вкладки и восстанавливает его при повторном выборе.
currentBackStackEntryAsState() — функция, возвращающая State<NavBackStackEntry?>, который обновляется при каждом изменении текущего маршрута. Это основной механизм синхронизации UI с навигацией: BottomNavigation выделяет активный элемент, Toolbar обновляет заголовок, Drawer закрывается при переходе.
Функция работает через snapshotFlow и collectAsState: при изменении back stack Compose перекомпоновывает подписанные элементы. Важно: currentBackStackEntryAsState() обновляется только после завершения анимации перехода. Для немедленного обновления используйте currentDestination, который изменяется синхронно с navigate(), но не поддерживает состояние.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
Для доступа к аргументам текущего маршрута используйте navBackStackEntry?.arguments. Это удобно в BottomNavigation: selectedItem вычисляется на основе currentRoute. Для отладки навигации используйте NavController.addOnDestinationChangedListener(), который логирует каждый переход. В production избегайте подписок внутри большого количества composable — создайте единый источник в ViewModel и передавайте State в UI.
Часто задаваемые вопросы
Технически да, но не рекомендуется. Единый NavController обеспечивает согласованный back stack и упрощает отладку. Множественные контроллеры оправданы только для вложенных графов с отдельной навигацией (например, modal bottom sheet со своим стеком).
Передавайте NavController в ViewModel через конструктор или DI. Однако лучше передавать только callback-функции (onNavigate, onBack), а не сам NavController — это упрощает тестирование. Для событий используйте Channel<NavEvent> в ViewModel и собирайте в UI.
Проблема в жизненном цикле: если NavController ещё не инициализирован (NavHost не построен), navigate() игнорируется. Используйте LaunchedEffect для вызова навигации после загрузки данных, а не внутри корутины с произвольным lifecycle.
Вызовите navController.navigate("target") { popUpTo(0) { inclusive = true } }. Параметр popUpTo(0) очищает стек полностью, inclusive = true удаляет и стартовую запись. Флаг launchSingleTop = true предотвращает дублирование нового маршрута.
NavHostController — наследник NavController с дополнительными методами для NavHost (например, setOnBackStackChangedListener). NavController — базовый класс, который можно использовать вне NavHost для программного управления стеком. В большинстве случаев используется NavHostController.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также