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