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() — е функция-разширение (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 от обхвата, което позволява организиране на преходи към други екрани. Такава архитектура прави навигацията изрична и предвидима.
@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() създава във вътрешния граф на NavHost възел с уникален идентификатор на маршрут. Когато NavController изпълни navigate(), библиотеката сравнява заявения маршрут с всички регистрирани composable възли и намира съответстващия. След съвпадение се създава NavBackStackEntry, който се поставя в стека за навигация и стартира композицията на UI.
Вътрешната имплементация на composable() използва механизма на мързелива инициализация: композицията на екрана се случва едва в момента на първия преход към този маршрут. Това означава, че екраните, към които потребителят никога не е навигирал, не заемат памет и не изпълняват никакъв код. Този подход значително подобрява производителността на приложения с голям брой екрани.
Параметърът key в composable() позволява управление на повторното създаване на екрана. По подразбиране composable не се създава отново при повторен преход към същия маршрут — NavHost използва съществуващия запис в стека. Ако обаче key бъде предаден и се промени, NavHost създава нова инстанция на composable функцията. Това е полезно за екрани с динамични данни, където трябва принудително да се актуализира състоянието при повторно отваряне.
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() поддържа гъвкава система от аргументи чрез параметъра arguments. Всеки аргумент се описва от обект NavArgument, който определя типа, стойността по подразбиране и задължителността. Аргументите се предават в маршрута като параметри на пътя (чрез фигурни скоби) или параметри на заявка (чрез въпросителен знак).
Параметрите на пътя се посочват директно в шаблона на маршрута: "profile/{userId}". При преход към маршрут "profile/42", NavHost автоматично извлича стойността 42 и я прави достъпна чрез backStackEntry.arguments. Параметрите на заявка се добавят след въпросителния знак: "search?query={text}" и също се анализират автоматично от библиотеката.
При извличане на аргументи е важно да се провери задължителността на параметъра чрез NavType.isNullableAllowed и да се предоставят стойности по подразбиране чрез NavArgument defaultValue. Ако задължителен параметър липсва, Navigation Compose генерира изключение IllegalArgumentException, което предотвратява невидими грешки с неправилни маршрути.
| Тип аргумент | NavType | Пример в маршрут |
|---|---|---|
| Int | NavType.IntType | "item/{id}" |
| String | NavType.StringType | "user/{name}" |
| Boolean | NavType.BoolType | "filter?enabled={value}" |
| Float | NavType.FloatType | "map/{lat}/{lon}" |
| Long | NavType.LongType | "article/{timestamp}" |
За предаване на сложни обекти се препоръчва използването на NavType.ParcelableType или NavType.SerializableType. Google обаче съветва да се минимизира размерът на предаваните данни — по-добре е да се предаде идентификатор и обектът да се зареди по идентификатор вътре в екрана. Това предотвратява проблеми с големи сериализирани данни и опростява обработката на конфигурационни промени.
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)
}
В реални приложения често се налага организиране на вложени навигационни графи — например отделен стек от екрани вътре в раздел на BottomNavigation. composable() поддържа вложеност чрез механизма на вложени NavHost: вътре в composable екран може да се декларира собствен NavHost с независим стек от маршрути.
Всеки вложен NavHost има собствен NavController и back stack. Това означава, че навигацията вътре в раздел не влияе на навигацията в други раздели — потребителят може свободно да превключва между разделите, без да губи историята на преходите във всеки от тях. Такава архитектура се нарича Scoped Navigation и се препоръчва от Google за приложения със сложна многослойна навигация.
При имплементиране на вложена навигация е важно правилно да се управлява състоянието на NavController: всеки вложен NavHost трябва да съхранява своя rememberNavController в обхвата на composable функцията. Според данни от Android Developer Summit 2024, повече от 40% от приложенията на Jetpack Compose с три или повече раздела използват архитектура на вложени NavHost за изолиране на навигацията между модулите.
// Основен 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() }
}
}
Преди Jetpack Compose стандартният метод за навигация в Android използваше Intent и FragmentManager. Intent — е системно съобщение, което стартира нова Activity, което предполага повторно създаване на цялото View дърво. За разлика от това, composable() работи вътре в една Activity и просто замества част от Compose дървото, което е значително по-бързо и по-ефективно по отношение на паметта.
Основни разлики между composable() и базираната на Intent навигация:
| Характеристика | composable() | Intent / Fragment |
|---|---|---|
| Архитектура | Single Activity, Compose дърво | Multi Activity, Fragment стекове |
| Предаване на данни | параметри на път/заявка, споделен ViewModel | Intent extras, Bundle, SharedPreferences |
| Дълбоки връзки | Вградена поддръжка на navDeepLink | intent-filter в манифеста |
| Back stack | Автоматично управление на popBackStack | FragmentManager.popBackStack() |
| Време за превключване | 5–15 ms (в рамките на процеса) | 50–200 ms (с повторно създаване) |
Преходът от Intent към composable() — не е просто смяна на API, а промяна на архитектурната парадигма. Вместо изрично посочване коя Activity трябва да се отвори, разработчикът декларативно описва всички възможни маршрути на едно място, което подобрява четливостта на кода и опростява тестването на навигацията. Според данни от Google I/O 2024, Jetpack Compose с Navigation Compose намалява количеството код за навигация с 40–60% в сравнение с FragmentManager.
Една от най-честите грешки — повторно създаване на 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, което осигурява правилно възстановяване на състоянието при превключване между раздели.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Често задавани въпроси
composable() — не е анотация, а функция-разширение на NavHost, която свързва маршрут с UI. Обикновената @Composable функция просто описва оформлението, докато composable() регистрира това оформление в навигационния граф с посочения маршрут, правейки го достъпно за навигация чрез NavController.
Препоръчва се предаване само на идентификатор (ID) чрез параметър на пътя, а самият обект да се зарежда на екрана по ID чрез хранилище или ViewModel. Ако обектът все пак трябва да бъде предаден, използвайте NavType.ParcelableType, но избягвайте предаване на обекти, по-големи от 1 KB — това може да доведе до TransactionTooLargeException.
Завъртането на екрана причинява промяна на конфигурацията, която по подразбиране пресъздава Activity. За да запазите състоянието на composable екрани, използвайте rememberSaveable за прости данни или ViewModel с обхвата на съответния екран. Navigation Compose възстановява back stack след пресъздаване, но състоянието вътре в composable() функциите се нулира без rememberSaveable.
Не, composable() — е функция-разширение на NavGraphBuilder, която е достъпна само вътре в блока NavHost. За проста замяна на част от UI без навигация използвайте условно рендериране (when, if) или AnimatedContent. composable() е предназначен именно за маршрутизация с поддръжка на back stack и дълбоки връзки.
Използвайте SavedStateHandle вътре в ViewModel: при първия преход handle.get("initialized") ще върне null, при връщане — запазената стойност. Алтернативно, анализирайте текущата позиция в back stack чрез navController.previousBackStackEntry — ако е null, това е първият екран в стека за навигация.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също