Jetpack Compose это декларативный UI: вы описываете, как экран выглядит для данного состояния, а фреймворк сам обновляет то, что изменилось. Главная тема собесов по Compose это рекомпозиция: когда она происходит, что пропускается и почему экран «тормозит».
Composable-функции
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(text = "Привет, $name", modifier = modifier.padding(16.dp))
}
Composable-функция ничего не возвращает, она «излучает» UI в дерево композиции. Аннотацию @Composable обрабатывает плагин компилятора Compose: он добавляет скрытый параметр Composer и группы, по которым рантайм отслеживает дерево. Поэтому composable можно вызывать только из другой composable.
Три фазы кадра:
- Composition: выполняются composable-функции, строится или обновляется дерево.
- Layout: измерение и расстановка элементов.
- Drawing: отрисовка.
Чтение состояния в каждой фазе вызывает повтор только этой и последующих фаз. Если состояние читается в лямбде Modifier.offset { } или drawBehind { }, при его изменении композиция не повторяется, только layout или отрисовка. Это главный приём оптимизации анимаций.
Рекомпозиция
Когда меняется состояние, которое прочитала composable-функция, Compose повторно вызывает эту функцию и её затронутых детей. Правила, которые нужно понимать:
- Рекомпозиция может пропустить функцию, если её параметры не изменились (skipping).
- Порядок и число вызовов не гарантированы, рекомпозиция может быть отменена и перезапущена. Поэтому composable-функции должны быть без побочных эффектов: никаких сетевых вызовов, записи в переменные снаружи, логирования аналитики прямо в теле.
- Функции вызываются часто, вплоть до каждого кадра анимации. Тяжёлые вычисления в теле нужно кешировать (
remember).
Стабильность и пропуск
Compose пропускает рекомпозицию функции, если все параметры стабильны и равны прошлым. Стабильный тип: изменения в нём видны Compose, а equals надёжен. Стабильны примитивы, String, функциональные типы (лямбды), классы только с val стабильных типов, State<T>, enum.
Нестабильны: классы с var-полями, интерфейсы коллекций List, Map, Set (под ними может быть изменяемая реализация), классы из модулей без компилятора Compose.
С Strong Skipping Mode (включён по умолчанию с Kotlin 2.0.20 и компилятора Compose в составе Kotlin) правила мягче: функции с нестабильными параметрами тоже пропускаются, если параметры равны по ссылке (===), а лямбды автоматически запоминаются. Но стабильность по-прежнему важна: новый экземпляр List на каждой рекомпозиции родителя не даст пропустить ребёнка.
Как сделать стабильным: неизменяемые data class с val, kotlinx.collections.immutable (ImmutableList), аннотации @Immutable и @Stable (обещание компилятору, проверять их никто не будет), файл конфигурации стабильности для классов из чужих модулей. Диагностика: отчёты компилятора Compose (composition metrics) и Layout Inspector со счётчиками рекомпозиций.
Modifier
Modifier это упорядоченная цепочка декораторов: размер, отступы, фон, клики, семантика. Порядок важен:
Modifier.padding(16.dp).background(Color.Red) // отступ снаружи фона
Modifier.background(Color.Red).padding(16.dp) // фон под отступом
Modifier.clickable { }.padding(8.dp) // кликабельна и область отступа
Хорошая практика: каждая переиспользуемая composable принимает modifier: Modifier = Modifier первым необязательным параметром и применяет его к корневому элементу.
Списки
Column с forEach создаёт все элементы сразу. Для длинных списков LazyColumn/LazyRow: компонуются только видимые элементы.
LazyColumn {
items(users, key = { it.id }) { user ->
UserRow(user)
}
}
key обязателен для изменяемых списков: без него состояние элементов привязано к позиции, и при вставке в начало «съедет», а анимации animateItem() не работают. contentType помогает переиспользовать композиции разных типов строк.
Compose и View
ComposeView вставляет Compose в View-иерархию, AndroidView наоборот встраивает View в Compose. Это позволяет мигрировать экран за экраном.
Частые ошибки
- Побочные эффекты прямо в теле composable (запрос, запись в переменную,
viewModel.load()без эффекта). - Тяжёлые вычисления в теле без
remember. - Передача нестабильных типов (
List, классы сvar) и создание новых объектов на каждой рекомпозиции. - Чтение быстро меняющегося состояния (скролл, анимация) в фазе композиции вместо layout/draw.
LazyColumnбезkey.- Неправильный порядок модификаторов.
Что спросят на собесе
- Что такое рекомпозиция и когда она происходит?
- Что такое стабильность типа и как она влияет на пропуск?
- Что изменил Strong Skipping Mode?
- Почему в composable нельзя делать побочные эффекты?
- Какие фазы у кадра Compose и как это использовать для оптимизации?
- Зачем
keyвLazyColumn?