Аннотация @Composable — фундаментальный элемент Jetpack Compose, который превращает обычную функцию Kotlin в декларативный строительный блок пользовательского интерфейса. Без этой аннотации невозможно создать ни один экран в современной Android-разработке. По данным Google Android Developers, 2026, более 80% новых проектов на Kotlin используют Compose для построения UI, а @Composable является самой часто используемой аннотацией в экосистеме.
Главное
@Composable — это аннотация языка Kotlin, которая маркирует функцию как предназначенную для описания пользовательского интерфейса в фреймворке Jetpack Compose. Когда компилятор Kotlin встречает эту аннотацию, он генерирует дополнительный код, позволяющий функции работать в контексте композиции — системы управления UI-деревом.
Аннотация @Composable была представлена Google в 2021 году вместе с первой стабильной версией Jetpack Compose 1.0. До её появления разработка интерфейсов под Android велась исключительно через XML-разметку и View-систему. @Composable кардинально изменила подход: вместо описания UI в отдельном файле разметки разработчик пишет интерфейс непосредственно на Kotlin.
Главное отличие @Composable от обычных функций Kotlin — способность читать и реагировать на изменения состояния. Когда переменная, которую читает Composable-функция, меняет значение, система автоматически планирует перезапуск (рекомпозицию) этой функции. Это избавляет разработчика от ручного обновления UI через findViewById и setText.
Внутренняя механика @Composable опирается на концепцию слота — специального участка памяти, который выделяется для каждой функции в составе композиции. В этом слоте хранятся значения, переданные в функцию, а также служебная информация, необходимая для сравнения при последующих вызовах.
Для объявления Composable-функции достаточно добавить аннотацию @Composable перед ключевым словом fun. При этом функция должна находиться в пакете, импортирующем аннотацию из androidx.compose.runtime. Имя функции рекомендуется писать с заглавной буквы — это общепринятое соглашение в сообществе Compose, которое визуально отличает UI-компоненты от обычных функций.
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
var count by remember { mutableStateOf(0) }
Column {
Text("Hello, $name!")
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
}
}
Параметры Composable-функции могут быть любыми — примитивные типы, строки, лямбды и даже другие Composable-функции, передаваемые через Slot API. Рекомендуется делать параметры иммутабельными (val), чтобы избежать побочных эффектов при рекомпозиции. Все изменяемые данные должны управляться через state-механизмы Compose.
Composable-функции не могут возвращать произвольное значение как обычные функции — их единственная задача построить или обновить фрагмент UI-дерева. Однако существуют специальные паттерны вроде State Hoisting, при котором Compose-функция принимает состояние и колбэки через параметры, оставаясь чистой и переиспользуемой.
Система Compose накладывает несколько строгих ограничений на то, как должны выглядеть и вести себя Composable-функции. Первое правило: Composable-функция может вызывать только другие Composable-функции или обычные функции, не имеющие побочных эффектов. Это обеспечивает предсказуемость композиции и корректную работу оптимизаций Compose.
Второе правило касается порядка выполнения. Compose имеет право вызывать Composable-функции в любом порядке, поэтому код в теле такой функции не должен полагаться на последовательность вызова соседних функций. Каждая Composable-функция должна быть самодостаточной на уровне своей позиции в UI-дереве.
Третье правило — запрет на побочные эффекты внутри самого тела Composable-функции. Операции вроде записи в базу данных, отправки сетевых запросов или изменения внешних переменных должны выполняться только внутри специальных эффектов LaunchedEffect, DisposableEffect или SideEffect. Нарушение этого правила приводит к непредсказуемому поведению при рекомпозициях.
Четвёртое правило: Composable-функции должны быть идемпотентными. Повторный вызов с теми же аргументами должен производить тот же UI. Это требование необходимо для корректной работы skipping-оптимизации, при которой Compose пропускает перерисовку функций, чьи входные данные не изменились.
// Correct: pure Composable function without side effects
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// Wrong: side effect inside the body
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // NOT ALLOWED
Text("Loading...")
}
Рассмотрим практический пример создания экрана профиля с использованием @Composable аннотации. Здесь мы демонстрируем комбинирование нескольких Composable-функций, работу с состоянием и модификаторами — ключевыми элементами любой Compose-разметки.
@Composable
fun ProfileScreen(userId: String) {
var isFollowed by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
ProfileHeader(userId = userId)
Spacer(modifier = Modifier.height(16.dp))
StatsRow(posts = 42, followers = 1280)
Spacer(modifier = Modifier.height(24.dp))
FollowButton(
isFollowed = isFollowed,
onToggle = { isFollowed = !isFollowed }
)
}
}
@Composable
fun ProfileHeader(userId: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = "https://example.com/avatars/$userId",
contentDescription = "User avatar")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "User #$userId", style = MaterialTheme.typography.headlineMedium)
}
}
@Composable
fun StatsRow(posts: Int, followers: Int) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
StatItem("Posts", posts)
StatItem("Followers", followers)
}
}
@Composable
fun StatItem(label: String, value: Int) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "$value", style = MaterialTheme.typography.headlineSmall)
Text(text = label, style = MaterialTheme.typography.bodySmall)
}
}
В примере каждая Composable-функция отвечает за свою часть экрана: ProfileScreen управляет общим состоянием и композицией дочерних функций, ProfileHeader отображает аватар и имя, а StatsRow — блок со статистикой. Такой подход следует принципу единой ответственности и упрощает переиспользование компонентов.
В Jetpack Compose существует три основных типа Composable-функций. Первый тип — контейнеры (Row, Column, Box, LazyColumn), которые определяют расположение дочерних элементов. Второй — элементы отображения (Text, Image, Icon, Button), которые рендерят конкретные UI-компоненты. Третий — пользовательские Composable-функции, которые комбинируют встроенные компоненты в переиспользуемые блоки.
Контейнеры отличаются от обычных элементов тем, что принимают content-лямбду — последний параметр типа @Composable () -> Unit. Именно этот механизм позволяет строить вложенные UI-деревья: каждый контейнер порождает дочернюю композицию со своим контекстом и областью памяти.
Пользовательские Composable-функции делятся на два подтипа: умные (smart) и глупые (dumb). Умные функции управляют состоянием и логикой — они содержат вызовы remember, LaunchedEffect и других API Compose. Глупые функции принимают все данные через параметры и только отображают их. Разделение на умные и глупые компоненты улучшает тестируемость и переиспользование кода.
| Тип | Пример | Назначение |
|---|---|---|
| Контейнер | Column, Row, Box | Управление расположением дочерних элементов |
| Элемент | Text, Image, Button | Отображение контента и обработка ввода |
| Пользовательский | ProfileCard, UserList | Комбинация стандартных компонентов |
Главное преимущество @Composable аннотации — возможность создавать переиспользуемые UI-компоненты без наследования и сложных иерархий классов. В отличие от View-системы, где каждый кастомный элемент требовал создания Java-класса с конструкторами, Composable-компонент — это просто функция Kotlin с параметрами.
Для обеспечения переиспользования применяется паттерн Slot API, при котором Composable-функция принимает content-лямбды для разных областей своего макета. Например, компонент Card может принимать отдельные content для заголовка, тела и нижней части, что делает его универсальным для любых экранов приложения.
Модификаторы (Modifier) играют ключевую роль в переиспользовании: они позволяют настраивать отступы, размеры, клики и анимации без изменения самого компонента. Рекомендуется всегда передавать Modifier как параметр Composable-функции по умолчанию: Modifier = Modifier — это стандартная практика, принятая в официальных библиотеках Google.
@Composable
fun SectionCard(
modifier: Modifier = Modifier,
title: String,
content: @Composable () -> Unit
) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
content()
}
}
}
Благодаря Slot API компонент SectionCard можно использовать на разных экранах с разным содержимым — формами, списками, текстовыми блоками. Комбинирование модификаторов и Slot API делает Compose-компоненты максимально гибкими без потери типобезопасности, которую обеспечивает Kotlin.
Часто задаваемые вопросы
@Composable функция выполняется в контексте композиции и может читать состояние, автоматически перезапускаясь при его изменении. Обычные функции Kotlin не имеют доступа к механизмам отслеживания состояния и не участвуют в построении UI-дерева.
Нет, Composable-функции можно вызывать только из других Composable-функций, поскольку требуется специальный контекст композиции. Для интеграции Compose-кода с обычным Kotlin используется метод setContent { } у Activity или ComposeView во View-системе.
Это соглашение об именовании, принятое в сообществе Compose. Заглавная буква визуально отличает UI-компоненты от обычных функций, следуя правилам именования классов. Это не требование компилятора, но рекомендуемая практика в документации Google.
Ограничений на количество нет. На практике большой экран может содержать 50–100 Composable-функций, включая встроенные компоненты (Text, Button) и пользовательские. Compose оптимизирует дерево функций и выполняет только те, чьи входные данные изменились.
Обычно Composable-функции возвращают Unit, так как их задача — построить UI. Однако существуют специализированные функции вроде remember и derivedStateOf, которые помечены @Composable и возвращают значения. Это исключение, а не правило.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также