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

            // Navigate with minimal data
navController.navigate("profile/42")

            // Retrieve arguments on the screen
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
// Main NavHost with tabs
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// Nested graph inside Home tab
@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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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