Анотація @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("Вітаю, $name!")
Button(onClick = { count++ }) {
Text("Натиснуто $count разів")
}
}
}
Параметри 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 пропускає перемальовування функцій, чиї вхідні дані не змінилися.
// Правильно: чиста Composable-функція без побічних ефектів
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// Неправильно: побічний ефект всередині тіла
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // НЕ ДОЗВОЛЕНО
Text("Завантаження...")
}
Розглянемо практичний приклад створення екрану профілю з використанням @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 = "Користувач #$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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також