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
// 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)
}
В реальных приложениях часто требуется организовать вложенные графы навигации — например, отдельный стек экранов внутри вкладки BottomNavigation. composable() поддерживает вложенность через механизм вложенных NavHost: внутри composable-экрана можно объявить собственный NavHost с независимым стеком маршрутов.
Каждый вложенный NavHost имеет собственный NavController и back stack. Это означает, что навигация внутри вкладки не влияет на навигацию в других вкладках — пользователь может свободно переключаться между вкладками, не теряя историю переходов внутри каждой из них. Такая архитектура называется Scoped Navigation и рекомендуется Google для приложений со сложной многоуровневой навигацией.
При реализации вложенной навигации важно правильно управлять состоянием NavController: каждый вложенный NavHost должен хранить свой rememberNavController внутри скоупа composable-функции. По данным Android Developer Summit 2024, более 40% приложений на Jetpack Compose с тремя и более вкладками используют архитектуру вложенных NavHost для изоляции навигации между модулями.
// 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() }
}
}
До 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также