Анотацията @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), за да се избегнат страничните ефекти при рекомпозиция. Всички променливи данни трябва да се управляват чрез механизмите за състояние на Compose.
Composable функциите не могат да връщат произволна стойност като обикновените функции — единствената им задача е да изградят или актуализират част от UI дървото. Съществуват обаче специални модели като State Hoisting, при който Compose функцията приема състояние и callback-ове чрез параметри, оставайки чиста и повторно използваема.
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 и други Compose API. Глупавите функции приемат всички данни чрез параметри и просто ги показват. Разделянето на умни и глупави компоненти подобрява тестването и повторната употреба на кода.
| Вид | Пример | Предназначение |
|---|---|---|
| Контейнер | 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също