@Composable: какво е това, анотация Compose и област на приложение

Автор: IT Sectr Публикувано: 2026-06-27 Време за четене: 8 мин

Анотацията @Composable — основен елемент на Jetpack Compose, която превръща обикновена Kotlin функция в декларативен строителен блок за потребителския интерфейс. Без тази анотация никой екран не може да бъде създаден в съвременното Android разработване. Според Google Android Developers, 2026, над 80% от новите проекти на Kotlin използват Compose за изграждане на UI, а @Composable е най-често използваната анотация в екосистемата.

Основни неща

  • @Composable — Kotlin анотация, която позволява на функция да описва UI декларативно
  • Composable функции могат да викат само други Composable функции, спазвайки контекста на композицията
  • Презапускане на Composable функции се случва при промяна на входните параметри или състоянието
  • Ред на извикване на Composable функции не е гарантиран — Compose оптимизира преизграждането на UI
  • Именуване на Composable функции следва PascalCase правилото, като всички компоненти в Compose

Какво е @Composable в Jetpack Compose

@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 функция, достатъчно е да добавите анотацията @Composable преди ключовата дума fun. Функцията трябва да е в пакет, който импортира анотацията от androidx.compose.runtime. Името на функцията се препоръчва да се пише с главна буква — това е общоприета конвенция в Compose общността, която визуално разграничава UI компонентите от обикновените функции.

kotlin
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-ове чрез параметри, оставайки чиста и повторно използваема.

Правила за Composable функции в Kotlin

Compose системата налага няколко строги ограничения върху това как Composable функциите трябва да изглеждат и да се държат. Първо правило: Composable функция може да вика само други Composable функции или обикновени функции без странични ефекти. Това осигурява предвидимост на композицията и правилното работа на Compose оптимизациите.

Второто правило се отнася до реда на изпълнение. Compose има правото да вика Composable функции в произволен ред, така че кодът в тялото на такава функция не трябва да разчита на последователността на извикване на съседните функции. Всяка Composable функция трябва да бъде самодостатъчна на нивото на своята позиция в UI дървото.

Трето правило — забрана на страничните ефекти в самото тяло на Composable функцията. Операции като запис в база данни, изпращане на мрежови заявки или промяна на външни променливи трябва да се извършват само в специални ефекти LaunchedEffect, DisposableEffect или SideEffect. Нарушаването на това правило води до непредвидимо поведение при рекомпозициите.

Четвърто правило: Composable функциите трябва да бъдат идемпотентни. Повторното извикване със същите аргументи трябва да произведе същия UI. Това изискване е необходимо за правилната работа на skipping оптимизацията, при която Compose прескача прерисуването на функции, чиито входни данни не са се променили.

kotlin
// Правилно: чиста 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. Тук демонстрираме комбинирането на няколко Composable функции, работа със състояние и модификатори — ключови елементи на всяко Compose оформление.

kotlin
@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 — блока със статистика. Този подход следва принципа на единната отговорност и опростява повторната употреба на компонентите.

Видове Composable функции и тяхното предназначение

В 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 и повторна употреба на компоненти

Основното предимство на анотация @Composable — възможността за създаване на повторно използваеми UI компоненти без наследяване и сложни йерархии от класове. За разлика от View системата, където всяка потребителска компонента изискваше създаване на Java клас с конструктори, Composable компонентата е просто Kotlin функция с параметри.

За осигуряване на повторната употреба се прилага моделът Slot API, при който Composable функцията приема content ламбди за различни области на своето оформление. Например, Card компонентата може да приема отделен content за заглавие, тяло и долна част, което я прави универсална за всички екрани на приложението.

Модификаторите (Modifier) играят ключова роля в повторната употреба: те позволяват настройка на разстояния, размери, кликове и анимации без да се променя самата компонента. Препоръчва се винаги да предавате Modifier като параметър на Composable функцията със стойност по подразбиране: Modifier = Modifier — това е стандартна практика, приета в официалните библиотеки на Google.

kotlin
@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 функция?

@Composable функцията се изпълнява в контекста на композицията и може да чете състояние, като се презапуска автоматично при неговата промяна. Обикновените Kotlin функции нямат достъп до механизмите за проследяване на състоянието и не участват в изграждането на UI дървото.

Може ли Composable функция да бъде извикана от обикновена функция?

Не, Composable функциите могат да бъдат извиквани само от други Composable функции, тъй като се изисква специален контекст на композицията. За интеграция на Compose код с обикновен Kotlin се използва методът setContent { } в Activity или ComposeView в View системата.

Защо Composable функциите се пишат с главна буква?

Това е конвенция за именуване, приета в Compose общността. Главната буква разграничава визуално UI компонентите от обикновените функции, следвайки правилата за именуване на класове. Това не е изискване на компилатора, а препоръчвана практика в документацията на Google.

Колко Composable функции могат да бъдат на един екран?

Няма ограничение на броя. На практика един голям екран може да съдържа 50–100 Composable функции, включително вградените компоненти (Text, Button) и потребителските. Compose оптимизира дървото от функции и изпълнява само тези, чиито входни данни са се променили.

Може ли Composable функция да върне стойност?

Обикновено Composable функциите връщат Unit, тъй като тяхната задача е да изградят UI. Съществуват обаче специализирани функции като remember и derivedStateOf, които са маркирани с @Composable и връщат стойности. Това е изключение, а не правило.

Обобщение

  • @Composable — анотация за декларативно описване на UI в Jetpack Compose
  • Composable функции се извикват само в други Composable функции в специален контекст
  • Идемпотентност — всяко повторно изпълнение със същите аргументи дава същия UI
  • Странични ефекти са забранени в тялото на функцията — само чрез LaunchedEffect и SideEffect
  • Slot API и Modifier осигуряват повторна употреба на компонентите без наследяване
  • Контейнерни функции (Row, Column, LazyColumn) приемат content ламбди за вложени елементи
  • Препоръка: предавайте Modifier като параметър на всяка потребителска Composable функция със стойност по подразбиране

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също