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. Това елиминира надбавката за създаване и унищожаване на Fragment, както и опростява тестирането — NavController може да бъде mock-нат чрез TestNavHostController.
NavController е тесно свързан с NavHost — контейнера, който изобразява текущия екран от графата. Без NavHost, NavController не може да показва composable функции, но запазва способността да управлява стека. В типичната архитектура, NavController се създава на ниво Activity или главен composable и се предава надолу в дървото на композицията чрез параметри.
Според Google, NavController е преминал през няколко основни издания. Версия 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() добавя запис в стека, което води до натрупване на 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) | Почистване до маршрут (маршрутът остава) | popBackStack("home", false) |
| popBackStack(route, true) | Почистване до и включително маршрута | 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. Използвайте Channel<NavEvent> в ViewModel и събирайте в UI.
Проблемът е в животния цикъл: ако NavController все още не е инициализиран (NavHost не е изграден), navigate() се игнорира. Използвайте LaunchedEffect за извикване на навигация след зареждане на данните.
Извикайте 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също