composable() — це функція бібліотеки Navigation Compose, яка реєструє екран у NavHost і пов'язує URL-маршрут із Compose-розміткою. Коли навігація переходить на заданий маршрут, Jetpack Compose викликає відповідну composable-функцію та відображає її як поточний екран. На відміну від FragmentManager або Intent-based навігації, composable() працює на рівні однієї Activity та повністю керується через Kotlin DSL. За даними Android Developers (2025), понад 73% сучасних Android-додатків, побудованих на Jetpack Compose, використовують саме Navigation Compose для організації зміни екранів.
Головне
composable() — це функція-розширення (extension function) об'єкта NavHost. Kotlin DSL дозволяє викликати її всередині блоку NavHost для декларативного опису всіх екранів додатку. Кожен виклик створює entry у навігаційному графі, пов'язуючи рядковий маршрут з composable-функцією. Коли користувач переходить на певний маршрут, NavHost відображає відповідний composable як поточний екран, приховуючи попередній.
Бібліотека Navigation Compose була представлена Google у 2021 році як альтернатива Fragment-based навігації для Jetpack Compose. Основна перевага — повна сумісність із Compose-парадигмою: composable() працює в тому ж життєвому циклі, що й інші компоненти Compose, без необхідності у FragmentManager або транзакціях. Це усуває клас помилок, пов'язаних із невідповідністю Fragment та Compose lifecycle.
Кожен 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() використовує механізм lazy-ініціалізації: композиція екрана відбувається лише в момент першого переходу на цей маршрут. Це означає, що екрани, на які користувач ніколи не переходив, не займають пам'ять та не виконують жодного коду. Такий підхід суттєво покращує продуктивність додатків з великою кількістю екранів.
Параметр key у composable() дозволяє керувати перестворенням екрана. За замовчуванням composable не перестворюється при повторному переході на той самий маршрут — NavHost використовує існуючий back stack entry. Однак, якщо передати 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, який визначає тип, значення за замовчуванням та обов'язковість. Аргументи передаються в маршруті як path-параметри (через фігурні дужки) або query-параметри (через знак питання).
Path-параметри вказуються прямо в шаблоні маршруту: "profile/{userId}". При переході за маршрутом "profile/42" NavHost автоматично витягує значення 42 та робить його доступним через backStackEntry.arguments. Query-параметри додаються після знака питання: "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 радить мінімізувати розмір даних, що передаються — краще передавати ідентифікатор та завантажувати об'єкт за ідентифікатором всередині екрана. Це запобігає проблемам з великими серіалізованими даними та спрощує обробку configuration changes.
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-based навігації:
| Характеристика | composable() | Intent / Fragment |
|---|---|---|
| Архітектура | Single Activity, Compose-дерево | Multi Activity, Fragment стеки |
| Передача даних | path/query параметри, shared ViewModel | Intent extras, Bundle, SharedPreferences |
| Глибокі посилання | Вбудована підтримка navDeepLink | intent-filter у маніфесті |
| Back stack | Автоматичне управління popBackStack | FragmentManager.popBackStack() |
| Час перемикання | 5–15 мс (всередині процесу) | 50–200 мс (з перестворенням) |
Перехід з 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() при кожному перемиканні вкладки додає новий entry у стек, а не повертає до існуючого. Для 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) через path-параметр, а сам об'єкт завантажувати на екрані по ID через репозиторій або ViewModel. Якщо об'єкт все ж потрібно передати, використовуйте NavType.ParcelableType, але уникайте передачі об'єктів розміром більше 1 КБ — це може призвести до TransactionTooLargeException.
Поворот екрана викликає configuration change, який за замовчуванням перестворює Activity. Щоб зберегти стан composable-екранів, використовуйте rememberSaveable для простих даних або ViewModel з scope даного екрана. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також