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("Add to cart")
}
}
}
}
В этом примере Composable-функция ProductItem принимает объект Product, модификатор и колбэк. Все три параметра иммутабельны, что гарантирует предсказуемое поведение при рекомпозиции. Модификатор передан как параметр с дефолтным значением — это стандартная практика, позволяющая вызывающей стороне настраивать отступы и размеры.
Внутри 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("Search products") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
when (products) {
is Loading -> CircularProgressIndicator()
is Empty -> Text("No results found")
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 |
| Колбэк | Лямбда без @Composable | onClick: () -> Unit |
В сообществе Compose сложилось несколько устоявшихся идиом, которые делают Composable-функции более читаемыми и предсказуемыми. Первая — State Hoisting: состояние поднимается на уровень выше, а Composable-функция получает его через параметры. Это делает функцию чистой и переиспользуемой в разных контекстах.
Вторая идиома — Event-driven параметры. Вместо того чтобы передавать в Composable-функцию ViewModel или useCase, передаются только конкретные колбэки: onSave, onDelete, onNavigateToDetail. Это снижает связанность и упрощает тестирование — для теста ProductItem не нужна ViewModel, только лямбда-заглушка.
Третья идиома — CompositionLocal для передачи общих данных через дерево композиции. Тема, плотность экрана, текущий Route — всё это передаётся через CompositionLocal, избегая цепочек параметров через десятки Composable-функций. Однако злоупотреблять CompositionLocal не стоит: явные параметры всегда предпочтительнее неявных зависимостей.
// State Hoisting: state lifted to the parent function
@Composable
fun CounterDisplay(
count: Int,
onIncrement: () -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Counter: $count", style = MaterialTheme.typography.headlineLarge)
Button(onClick = onIncrement) {
Text("+1")
}
}
}
// Usage with 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 несовместима с не-Upload возвращаемыми типами.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также