Composable Function — е фундаменталната единица на потребителския интерфейс в Jetpack Compose, която определя как трябва да изглежда и се държи част от екрана. Всяка такава функция е маркирана с анотация @Composable и се изпълнява в специален контекст, позволяващ на Compose да проследява зависимости и автоматично да преизгражда UI при промяна на данни. Според Google Android Developers, 2026, правилното изграждане на Composable функции пряко влияе върху производителността на приложението и ефективността на рекомпозицията.
Основни точки
Composable Function — е функция в езика Kotlin, маркирана с анотация @Composable, която описва част от потребителския интерфейс по декларативен начин. Вместо да създава и конфигурира View обекти чрез Java код или XML маркировка, разработчикът просто пише как UI трябва да изглежда при всяко състояние на данните.
Основната разлика между Composable функция и традиционната View система на Android се крие в модела на обновяване. В класическия подход разработчикът ръчно извикваше findViewById, променяше текст чрез setText, управляваше видимост чрез setVisibility. Composable Function освобождава от тази рутина: при промяна на данните системата сама определя кои функции трябва да бъдат рестартирани и изпълнява само тях.
Компилаторът на Kotlin, обработвайки анотация @Composable, генерира допълнителен код, който интегрира функцията в механизма на композиция. Този код включва четене и запис в слотове — специални клетки памет, съхраняващи състоянието и параметрите на всяка Composable функция в текущото UI дърво. Благодарение на тази интеграция, Compose знае кои функции зависят от кои данни.
Синтаксисът на Composable функция е максимално кратак: достатъчно е да добавите @Composable преди ключовата дума fun. Функцията може да приема всякакви параметри, да включва други Composable извиквания в тялото си и да използва Kotlin конструкции — условия, цикли, when изрази — за условно показване на UI.
@Composable
fun ProductItem(
product: Product,
modifier: Modifier = Modifier,
onAddToCart: () -> Unit
) {
Card(modifier = modifier.padding(8.dp)) {
Row(modifier = Modifier.fillMaxWidth().padding(12.dp),
verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.weight(1f)) {
Text(text = product.name, style = MaterialTheme.typography.titleMedium)
Text(text = "${product.price}", color = MaterialTheme.colorScheme.primary)
}
Button(onClick = onAddToCart) {
Text("Добави в кошницата")
}
}
}
}
В този пример Composable функцията ProductItem приема обект Product, модификатор и callback. И трите параметъра са неизменяеми, което гарантира предвидимо поведение при рекомпозиция. Модификаторът е предаден като параметър със стойност по подразбиране — това е стандартна практика, позволяваща на извикващата страна да персонализира полетата и размерите.
Вътре в Composable функция се използват вградени компоненти на Material Design (Text, Button, Card, TextField) или фундаментални примитиви (Canvas, Layout). Всеки компонент приема параметри за конфигуриране на външния вид и поведението, както и един или повече модификатори чрез параметъра modifier.
Модификаторите (Modifier) — са верига от функции, които променят размера, позицията, обработката на събития и външния вид на компонента. Редът на модификаторите във веригата има значение: clickable.semantics работи различно от semantics.clickable, а padding.background оцветява фона на областта включително padding, което е критично при проектиране.
Вътре в Composable функция могат да се използват условия if и when за условно показване на части от UI, както и цикли for за динамични списъци. Всички тези конструкции работят естествено, тъй като Kotlin е пълноценен език за програмиране. Важно е обаче да се помни: ако условието или цикълът съдържа извиквания на Composable функции, те също участват в рекомпозиция.
@Composable
fun ProductList(
products: List<Product>,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier) {
items(products, key = { it.id }) { product ->
ProductItem(
product = product,
onAddToCart = { /* add to cart */ }
)
}
}
}
Нека разгледаме пример за екран за търсене на продукти, използващ няколко Composable функции. Тук са показани типични модели: поле за въвеждане със състояние, филтриране на списък, обработка на празен резултат и зареждане.
data class Product(
val id: String,
val name: String,
val price: Double,
val category: String
)
@Composable
fun SearchScreen() {
var query by remember { mutableStateOf("") }
val products = remember(query) { getFilteredProducts(query) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
OutlinedTextField(
value = query,
onValueChange = { query = it },
label = { Text("Търсене на продукти") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
when (products) {
is Loading -> CircularProgressIndicator()
is Empty -> Text("Няма намерени резултати")
is Result -> LazyColumn {
items(products.items, key = { it.id }) { product ->
ProductItem(product = product, onAddToCart = {})
}
}
}
}
}
Този пример демонстрира няколко идиома едновременно: remember за запазване на състоянието на заявката за търсене, remember(query) за филтриране с ключ, when за три състояния на UI и LazyColumn за ефективно показване на списък. Всеки от тези идиоми е резултат от практически опит в разработката на Compose приложения.
Composable функциите приемат параметри точно както обикновените Kotlin функции, но с една важна разлика: параметърът може да бъде друга Composable функция, предадена чрез ламбда с анотация @Composable. Този механизъм се нарича Slot API и е основният модел за създаване на контейнери за многократна употреба.
Slot API решава проблема, който в традиционната View система се решаваше чрез ViewGroup и програмно добавяне на дъщерни View. Вместо методи addView, Compose използва content ламбди — последния параметър с тип @Composable () -> Unit. Извикващата страна предава произволен UI на тази ламбда, а самият контейнер определя само разположението му.
Параметрите на Composable функция могат да имат стойности по подразбиране, което опростява използването им в различни контексти. Препоръчва се да правите задължителни само онези параметри, без които функцията не може да изпълни задачата си, а останалите да снабдите с разумни стойности по подразбиране.
| Параметър | Тип | Пример |
|---|---|---|
| Задължителен | Всякакъв тип | name: String |
| Незадължителен | Със стойност по подразбиране | modifier: Modifier = Modifier |
| Content | @Composable () -> Unit | content: @Composable () -> Unit |
| Callback | Ламбда без @Composable | onClick: () -> Unit |
В общността на Compose са се оформили няколко установени идиома, които правят Composable функциите по-четими и предвидими. Първият — State Hoisting: състоянието се издига на по-високо ниво, а Composable функцията го получава чрез параметри. Това прави функцията чиста и използваема многократно в различни контексти.
Вторият идиом — Event-driven параметри. Вместо да предавате ViewModel или useCase на Composable функцията, се предават само конкретни callback-и: onSave, onDelete, onNavigateToDetail. Това намалява свързаността и опростява тестването — за тест на ProductItem не е нужен ViewModel, а само ламбда-заглушка.
Третият идиом — CompositionLocal за предаване на общи данни чрез дървото на композиция. Тема, плътност на екрана, текущ маршрут — всичко това се предава чрез CompositionLocal, избягвайки вериги от параметри през десетки Composable функции. Не трябва обаче да се злоупотребява с CompositionLocal: изричните параметри винаги са за предпочитане пред неявните зависимости.
// State Hoisting: състоянието издигнато до родителската функция
@Composable
fun CounterDisplay(
count: Int,
onIncrement: () -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Брояч: $count", style = MaterialTheme.typography.headlineLarge)
Button(onClick = onIncrement) {
Text("+1")
}
}
}
// Използване с State Hoisting
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
CounterDisplay(
count = count,
onIncrement = { count++ }
)
}
Често задавани въпроси
Да, return е допустим, но с внимание. Compose оптимизира рекомпозиция на ниво отделни функции и ранният return може да наруши тази оптимизация. По-добре е да използвате условни оператори if или when вътре в тялото на функцията.
В Kotlin Unit е обект-сингълтън, а не празен тип. Composable функциите връщат Unit, което технически означава, че връщат самия обект Unit. На практика обаче това няма значение — върнатата стойност се игнорира от системата за композиция.
Предаването на променливи колекции е възможно, но е лоша практика. Ако колекцията се промени, Compose няма да разбере за това, тъй като референцията към обекта остава същата. Използвайте immutable списъци или mutableStateListOf за проследими промени.
За дебъгване използвайте Android Studio с Layout Inspector, който показва текущото дърво на Composable функциите, стойностите на параметрите и причините за рекомпозиция. Работи и обикновеният дебъгер на Kotlin — точките на прекъсване вътре в Composable функциите се задействат правилно при всяка рекомпозиция.
Composable функция винаги връща Unit, затова return type не се посочва. Опитът за връщане на друг тип ще предизвика грешка при компилация, тъй като анотацията @Composable е несъвместима с връщани типове, различни от Unit.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също