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. Това елиминира надбавката за създаване и унищожаване на 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.

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() добавя запис в стека, което води до натрупване на 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)Почистване до маршрут (маршрутът остава)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: запазване на състоянието на екрана

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. Използвайте Channel<NavEvent> в ViewModel и събирайте в UI.

Защо navigate не работи след асинхронна операция?

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

Как да изчистя цялия 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също