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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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