composable(): какво е, NavHost и маршрутизация в Jetpack Compose

Автор: IT Sectr Публикувано: 2026-06-30 Време за четене: 9 мин

composable() — е функция на библиотеката Navigation Compose, която регистрира екран в NavHost и свързва URL маршрут с Compose оформление. Когато навигацията премине към зададен маршрут, Jetpack Compose извиква съответната composable функция и я показва като текущ екран. За разлика от FragmentManager или навигацията, базирана на Intent, composable() работи на ниво една Activity и се управлява изцяло чрез Kotlin DSL. Според данни на Android Developers (2025), повече от 73% от съвременните Android приложения, изградени на Jetpack Compose, използват именно Navigation Compose за организиране на смяната на екрани.

Основни точки

  • composable() — функция за регистриране на екран в NavHost на библиотеката Navigation Compose.
  • Маршрут — всеки екран се идентифицира от текстов маршрут, предаван като първи аргумент.
  • Параметри — composable() поддържа аргументи чрез NavArgument, включително задължителни и опционални.
  • Вложеност — вложената навигация се поддържа чрез вложени NavHost с отделни графи на маршрути.
  • Производителност — composable() използва мързелива инициализация: екранът се създава само при първия преход.

Какво е composable() в NavHost

composable() — е функция-разширение (extension function) на обекта NavHost. Kotlin DSL позволява да се извиква вътре в блока NavHost за декларативно описание на всички екрани на приложението. Всяко извикване създава запис в навигационния граф, свързвайки текстов маршрут с composable функция. Когато потребителят навигира до конкретен маршрут, NavHost показва съответния composable като текущ екран, скривайки предишния.

Библиотеката Navigation Compose беше представена от Google през 2021 г. като алтернатива на базираната на Fragment навигация за Jetpack Compose. Основното предимство — пълна съвместимост с парадигмата на Compose: composable() работи в същия жизнен цикъл като останалите компоненти на Compose, без необходимост от FragmentManager или транзакции. Това елиминира клас грешки, свързани с несъответствие на жизнения цикъл на Fragment и Compose.

Всеки composable() приема текстов маршрут (route) и ламбда функция, която получава обект NavBackStackEntry и връща Composable UI. Вътре в ламбдата може да се достъпи NavController чрез извикване на navController от обхвата, което позволява организиране на преходи към други екрани. Такава архитектура прави навигацията изрична и предвидима.

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate("profile")
                }
            )
        }
        composable("profile") {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

Как работи composable(): ключове и параметри

Всяко извикване на composable() създава във вътрешния граф на NavHost възел с уникален идентификатор на маршрут. Когато NavController изпълни navigate(), библиотеката сравнява заявения маршрут с всички регистрирани composable възли и намира съответстващия. След съвпадение се създава NavBackStackEntry, който се поставя в стека за навигация и стартира композицията на UI.

Вътрешната имплементация на composable() използва механизма на мързелива инициализация: композицията на екрана се случва едва в момента на първия преход към този маршрут. Това означава, че екраните, към които потребителят никога не е навигирал, не заемат памет и не изпълняват никакъв код. Този подход значително подобрява производителността на приложения с голям брой екрани.

Параметърът key в composable() позволява управление на повторното създаване на екрана. По подразбиране composable не се създава отново при повторен преход към същия маршрут — NavHost използва съществуващия запис в стека. Ако обаче key бъде предаден и се промени, NavHost създава нова инстанция на composable функцията. Това е полезно за екрани с динамични данни, където трябва принудително да се актуализира състоянието при повторно отваряне.

kotlin
val NavGraphBuilder.Composable: Unit
    get() = composable(
        route = "details/{itemId}",
        arguments = listOf(
            NavArgument("itemId") { 
                type = NavType.IntType
            }
        ),
        deepLinks = listOf(
            navDeepLink { uriPattern = "myapp://details/{itemId}" }
        )
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailsScreen(itemId = itemId)
    }

Предаване на аргументи чрез composable()

composable() поддържа гъвкава система от аргументи чрез параметъра arguments. Всеки аргумент се описва от обект NavArgument, който определя типа, стойността по подразбиране и задължителността. Аргументите се предават в маршрута като параметри на пътя (чрез фигурни скоби) или параметри на заявка (чрез въпросителен знак).

Параметрите на пътя се посочват директно в шаблона на маршрута: "profile/{userId}". При преход към маршрут "profile/42", NavHost автоматично извлича стойността 42 и я прави достъпна чрез backStackEntry.arguments. Параметрите на заявка се добавят след въпросителния знак: "search?query={text}" и също се анализират автоматично от библиотеката.

При извличане на аргументи е важно да се провери задължителността на параметъра чрез NavType.isNullableAllowed и да се предоставят стойности по подразбиране чрез NavArgument defaultValue. Ако задължителен параметър липсва, Navigation Compose генерира изключение IllegalArgumentException, което предотвратява невидими грешки с неправилни маршрути.

Тип аргументNavTypeПример в маршрут
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.LongType"article/{timestamp}"

За предаване на сложни обекти се препоръчва използването на NavType.ParcelableType или NavType.SerializableType. Google обаче съветва да се минимизира размерът на предаваните данни — по-добре е да се предаде идентификатор и обектът да се зареди по идентификатор вътре в екрана. Това предотвратява проблеми с големи сериализирани данни и опростява обработката на конфигурационни промени.

kotlin
data class Profile(val id: Int, val name: String) : Parcelable

            // Навигирайте с минимални данни
navController.navigate("profile/42")

            // Извлечете аргументи на екрана
composable(
    route = "profile/{userId}",
    arguments = listOf(
        NavArgument("userId") { type = NavType.IntType }
    )
) { backStackEntry ->
    val userId = backStackEntry.arguments?.getInt("userId") ?: 0
    ProfileDetailScreen(userId = userId)
}

Вложена навигация с composable()

В реални приложения често се налага организиране на вложени навигационни графи — например отделен стек от екрани вътре в раздел на BottomNavigation. composable() поддържа вложеност чрез механизма на вложени NavHost: вътре в composable екран може да се декларира собствен NavHost с независим стек от маршрути.

Всеки вложен NavHost има собствен NavController и back stack. Това означава, че навигацията вътре в раздел не влияе на навигацията в други раздели — потребителят може свободно да превключва между разделите, без да губи историята на преходите във всеки от тях. Такава архитектура се нарича Scoped Navigation и се препоръчва от Google за приложения със сложна многослойна навигация.

При имплементиране на вложена навигация е важно правилно да се управлява състоянието на NavController: всеки вложен NavHost трябва да съхранява своя rememberNavController в обхвата на composable функцията. Според данни от Android Developer Summit 2024, повече от 40% от приложенията на Jetpack Compose с три или повече раздела използват архитектура на вложени NavHost за изолиране на навигацията между модулите.

kotlin
// Основен NavHost с раздели
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// Вложен граф в раздела Home
@Composable
fun HomeNavGraph() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "home_feed"
    ) {
        composable("home_feed") { FeedScreen() }
        composable("home_detail/{postId}") { PostDetailScreen() }
    }
}

Сравнение на composable() с Intent навигация

Преди Jetpack Compose стандартният метод за навигация в Android използваше Intent и FragmentManager. Intent — е системно съобщение, което стартира нова Activity, което предполага повторно създаване на цялото View дърво. За разлика от това, composable() работи вътре в една Activity и просто замества част от Compose дървото, което е значително по-бързо и по-ефективно по отношение на паметта.

Основни разлики между composable() и базираната на Intent навигация:

  • Скорост — composable() превключва екрани за милисекунди без повторно създаване на Activity, Intent изисква рестартиране на Activity.
  • Анимации — в Navigation Compose анимациите на преходите се задават декларативно чрез AnimatedNavHost, без необходимост от overridePendingTransition.
  • Споделено състояние — composable() работи в общ обхват на ViewModel, което опростява предаването на данни между екрани без Intent extras.
Характеристикаcomposable()Intent / Fragment
АрхитектураSingle Activity, Compose дървоMulti Activity, Fragment стекове
Предаване на даннипараметри на път/заявка, споделен ViewModelIntent extras, Bundle, SharedPreferences
Дълбоки връзкиВградена поддръжка на navDeepLinkintent-filter в манифеста
Back stackАвтоматично управление на popBackStackFragmentManager.popBackStack()
Време за превключване5–15 ms (в рамките на процеса)50–200 ms (с повторно създаване)

Преходът от Intent към composable() — не е просто смяна на API, а промяна на архитектурната парадигма. Вместо изрично посочване коя Activity трябва да се отвори, разработчикът декларативно описва всички възможни маршрути на едно място, което подобрява четливостта на кода и опростява тестването на навигацията. Според данни от Google I/O 2024, Jetpack Compose с Navigation Compose намалява количеството код за навигация с 40–60% в сравнение с FragmentManager.

Типични грешки с composable()

Една от най-честите грешки — повторно създаване на NavController при рекомпозиция. Ако NavController се създава чрез rememberNavController() на нивото на родителския composable, който може да бъде повторно създаден при промяна на състоянието, навигацията се разрушава — историята на преходите се губи. Правилното решение е повдигане на NavController на нивото на стабилен composable, например на нивото на Activity или коренния composable на приложението.

Вторият често срещан проблем — безкрайна рекомпозиция при навигация. Това се случва, когато извикването navController.navigate() е поставено директно в тялото на composable функцията. Тъй като навигацията променя състоянието на NavHost, това задейства рекомпозиция, която отново извиква navigate(), създавайки цикъл. Всички извиквания на навигация трябва да бъдат обвити в ламбда манипулатори (onClick, onButtonPressed), а не да се изпълняват в композицията.

Третата грешка — неправилно управление на back stack при използване на BottomNavigation. Простата навигация чрез navigate() при всяко превключване на раздел добавя нов запис в стека, вместо да се върне към съществуващия. За BottomNavigation трябва да се използва navController.navigate() с restoreState = true и launchSingleTop = true, което осигурява правилно възстановяване на състоянието при превключване между раздели.

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

Често задавани въпроси

Каква е разликата между composable() и обикновена функция @Composable?

composable() — не е анотация, а функция-разширение на NavHost, която свързва маршрут с UI. Обикновената @Composable функция просто описва оформлението, докато composable() регистрира това оформление в навигационния граф с посочения маршрут, правейки го достъпно за навигация чрез NavController.

Как да предам сложен обект между composable() екрани?

Препоръчва се предаване само на идентификатор (ID) чрез параметър на пътя, а самият обект да се зарежда на екрана по ID чрез хранилище или ViewModel. Ако обектът все пак трябва да бъде предаден, използвайте NavType.ParcelableType, но избягвайте предаване на обекти, по-големи от 1 KB — това може да доведе до TransactionTooLargeException.

Защо composable() екранът се създава отново при завъртане на екрана?

Завъртането на екрана причинява промяна на конфигурацията, която по подразбиране пресъздава Activity. За да запазите състоянието на composable екрани, използвайте rememberSaveable за прости данни или ViewModel с обхвата на съответния екран. Navigation Compose възстановява back stack след пресъздаване, но състоянието вътре в composable() функциите се нулира без rememberSaveable.

Може ли composable() да се използва без NavHost?

Не, composable() — е функция-разширение на NavGraphBuilder, която е достъпна само вътре в блока NavHost. За проста замяна на част от UI без навигация използвайте условно рендериране (when, if) или AnimatedContent. composable() е предназначен именно за маршрутизация с поддръжка на back stack и дълбоки връзки.

Как да различа първия преход от връщането назад в composable()?

Използвайте SavedStateHandle вътре в ViewModel: при първия преход handle.get("initialized") ще върне null, при връщане — запазената стойност. Алтернативно, анализирайте текущата позиция в back stack чрез navController.previousBackStackEntry — ако е null, това е първият екран в стека за навигация.

Обобщение

  • composable() — функция за регистриране на екран в NavHost, основният начин за организиране на навигация в Jetpack Compose.
  • Маршрути — всеки екран се идентифицира от текст на маршрут с опционални параметри на пътя и заявка.
  • Аргументи — предават се чрез NavArgument с поддръжка за примитивни, Parcelable и Serializable типове.
  • Вложеност — composable() поддържа вложени NavHost за организиране на модулна навигация с независими стекове.
  • Производителност — мързеливата инициализация на екрани спестява памет, времето за превключване между екрани е 5–15 ms.
  • Грешки — основни проблеми: пресъздаване на NavController, безкрайна рекомпозиция при извикване на navigate() в тялото на composable, неправилна работа на BottomNavigation.
  • Миграция — преходът от FragmentManager към composable() намалява обема на кода за навигация с 40–60% и елиминира клас грешки, свързани с жизнения цикъл на Fragment.

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

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

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

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