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

Автор: IT Sectr Опубліковано: 2026-06-30 Час читання: 9 хв

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() — функція реєстрації екрана в NavHost бібліотеки Navigation Compose.
  • Маршрут — кожен екран ідентифікується рядковим маршрутом, що передається першим аргументом.
  • Параметри — composable() підтримує аргументи через NavArgument, включаючи обов'язкові та опціональні.
  • Вкладеність — підтримується вкладена навігація через вкладені NavHost з окремими графами маршрутів.
  • Продуктивність — composable() використовує lazy-ініціалізацію: екран створюється лише при першому переході.

Що таке composable() у NavHost

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 зі скоупу, що дозволяє організувати переходи на інші екрани. Така архітектура робить навігацію явною та передбачуваною.

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() використовує механізм lazy-ініціалізації: композиція екрана відбувається лише в момент першого переходу на цей маршрут. Це означає, що екрани, на які користувач ніколи не переходив, не займають пам'ять та не виконують жодного коду. Такий підхід суттєво покращує продуктивність додатків з великою кількістю екранів.

Параметр key у composable() дозволяє керувати перестворенням екрана. За замовчуванням composable не перестворюється при повторному переході на той самий маршрут — NavHost використовує існуючий back stack entry. Однак, якщо передати 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, який визначає тип, значення за замовчуванням та обов'язковість. Аргументи передаються в маршруті як path-параметри (через фігурні дужки) або query-параметри (через знак питання).

Path-параметри вказуються прямо в шаблоні маршруту: "profile/{userId}". При переході за маршрутом "profile/42" NavHost автоматично витягує значення 42 та робить його доступним через backStackEntry.arguments. Query-параметри додаються після знака питання: "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 радить мінімізувати розмір даних, що передаються — краще передавати ідентифікатор та завантажувати об'єкт за ідентифікатором всередині екрана. Це запобігає проблемам з великими серіалізованими даними та спрощує обробку configuration changes.

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-based навігації:

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

Перехід з 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() при кожному перемиканні вкладки додає новий entry у стек, а не повертає до існуючого. Для 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) через path-параметр, а сам об'єкт завантажувати на екрані по ID через репозиторій або ViewModel. Якщо об'єкт все ж потрібно передати, використовуйте NavType.ParcelableType, але уникайте передачі об'єктів розміром більше 1 КБ — це може призвести до TransactionTooLargeException.

Чому composable() екран перестворюється при повороті екрана?

Поворот екрана викликає configuration change, який за замовчуванням перестворює Activity. Щоб зберегти стан composable-екранів, використовуйте rememberSaveable для простих даних або ViewModel з scope даного екрана. 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.
  • Маршрути — кожен екран ідентифікується рядком маршруту з опціональними path- та query-параметрами.
  • Аргументи — передаються через NavArgument з підтримкою примітивних, Parcelable та Serializable типів.
  • Вкладеність — composable() підтримує вкладені NavHost для організації модульної навігації з незалежними стеками.
  • Продуктивність — lazy-ініціалізація екранів економить пам'ять, швидкість перемикання між екранами становить 5–15 мс.
  • Помилки — головні проблеми: перестворення NavController, нескінченна рекомпозиція при виклику navigate() у тілі composable, неправильна робота BottomNavigation.
  • Міграція — перехід з FragmentManager на composable() скорочує об'єм коду навігації на 40–60% та усуває клас помилок, пов'язаних із lifecycle Fragment.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також