NavController — суть, методы и управление навигацией в Jetpack Compose

Автор: IT Sectr Опубликовано: 2026-06-29 Время чтения: 7 мин

NavController — это центральный компонент библиотеки Navigation Compose, управляющий стеком навигации и состоянием back stack в Android-приложениях. Через NavController выполняются переходы между экранами, возврат на предыдущие страницы и передача данных между маршрутами. По данным Android Developers (2025), NavController является обязательным элементом любого Compose-приложения с более чем одним экраном. Контроллер создаётся через rememberNavController(), передаётся в NavHost и доступен для вызова navigate() из любой точки композиции. Встроенная поддержка SavedStateHandle автоматически сохраняет состояние ViewModel при реконфигурации.

Главное

  • NavController — центральный контроллер навигации Compose, управляющий back stack и переходами между экранами
  • navigate() — основной метод для перехода на маршрут с поддержкой NavOptions для управления стеком
  • popBackStack() — возврат на предыдущий экран с опциональной очисткой до указанного маршрута
  • SavedStateHandle — интеграция с ViewModel для сохранения состояния экрана при навигации
  • currentBackStackEntryAsState() — наблюдение за текущим маршрутом для синхронизации UI

Что такое NavController в Jetpack Compose?

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.

kotlin
@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.

kotlin
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: управление возвратом и очисткой стека

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)Очистка до и включая routepopBackStack("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: сохранение состояния экрана

SavedStateHandle — это механизм сохранения состояния ViewModel при навигации и реконфигурации. NavController автоматически предоставляет SavedStateHandle для каждой NavBackStackEntry. Через SavedStateHandle ViewModel хранит состояние экрана и восстанавливает его при возврате (restoreState = true).

В Navigation Compose SavedStateHandle используется совместно с ViewModel: ViewModel инициализируется через SavedStateHandle, который передаётся из backStackEntry. При переходе на другой экран и возврате (с restoreState) ViewModel получает сохранённое состояние, а не создаётся заново. Это критично для экранов с вводом данных, фильтрами или скроллом.

kotlin
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

currentBackStackEntryAsState() — функция, возвращающая State<NavBackStackEntry?>, который обновляется при каждом изменении текущего маршрута. Это основной механизм синхронизации UI с навигацией: BottomNavigation выделяет активный элемент, Toolbar обновляет заголовок, Drawer закрывается при переходе.

Функция работает через snapshotFlow и collectAsState: при изменении back stack Compose перекомпоновывает подписанные элементы. Важно: currentBackStackEntryAsState() обновляется только после завершения анимации перехода. Для немедленного обновления используйте currentDestination, который изменяется синхронно с navigate(), но не поддерживает состояние.

kotlin
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 в одном Activity?

Технически да, но не рекомендуется. Единый NavController обеспечивает согласованный back stack и упрощает отладку. Множественные контроллеры оправданы только для вложенных графов с отдельной навигацией (например, modal bottom sheet со своим стеком).

Как передать NavController через ViewModel?

Передавайте NavController в ViewModel через конструктор или DI. Однако лучше передавать только callback-функции (onNavigate, onBack), а не сам NavController — это упрощает тестирование. Для событий используйте Channel<NavEvent> в ViewModel и собирайте в UI.

Почему navigate не срабатывает после асинхронной операции?

Проблема в жизненном цикле: если NavController ещё не инициализирован (NavHost не построен), navigate() игнорируется. Используйте LaunchedEffect для вызова навигации после загрузки данных, а не внутри корутины с произвольным lifecycle.

Как очистить весь back stack и перейти на новый экран?

Вызовите navController.navigate("target") { popUpTo(0) { inclusive = true } }. Параметр popUpTo(0) очищает стек полностью, inclusive = true удаляет и стартовую запись. Флаг launchSingleTop = true предотвращает дублирование нового маршрута.

Чем отличается NavHostController от NavController?

NavHostController — наследник NavController с дополнительными методами для NavHost (например, setOnBackStackChangedListener). NavController — базовый класс, который можно использовать вне NavHost для программного управления стеком. В большинстве случаев используется NavHostController.

Итоги

  • NavController — центральный компонент Navigation Compose, управляющий стеком маршрутов и переходами между экранами
  • navigate() выполняет переход с настройками popUpTo, launchSingleTop и restoreState через NavOptions
  • popBackStack() управляет возвратом: одиночный шаг или массовая очистка до указанного маршрута с inclusive
  • SavedStateHandle интегрируется с ViewModel для автоматического сохранения состояния экрана при навигации
  • currentBackStackEntryAsState() предоставляет реактивное наблюдение за текущим маршрутом для синхронизации UI
  • BackHandler обрабатывает системную кнопку Back, а PredictiveBackGesture поддерживается с NavController 2.9.0
  • Для тестирования используйте TestNavHostController с compose-test-rule и Semantics-матчерами

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также