Прямые ссылки на публичные уроки для быстрого старта и стабильной индексации lesson-страниц.
Мы уже научились отображать и оформлять элементы. Но часто данные являются динамическими — заполняются строками, цифрами и другими значениями в зависимости от состояния. В Jetpack Compose за это отвечает state. В этом уроке рассмотрим важные концепции состояния и рекомпозиции.
В классическом подходе на XML, когда нужно было изменить значение элемента, вызывался метод, который обновлял его внутреннее состояние.
В Compose, чтобы изменить состояние composable элемента, нужно вызвать эту функцию заново с новыми данными — тогда интерфейс будет перерисован. Зафиксируем: чтобы перерисовать элемент — нужно повторно вызвать эту же composable функцию.
Jetpack Compose — декларативный фреймворк. Мы описываем что должно отображаться на экране, а не как это сделать пошагово. Когда данные изменяются, Compose автоматически обновляет UI.
Для этого Compose необходимо отслеживать изменения состояния и выполнять рекомпозицию — процесс повторного вызова Composable функций для обновления UI.
Чтобы рекомпозиция происходила автоматически, состояние нужно где-то хранить — но об этом чуть позже.
Весь код из предыдущих уроков перенесен в отдельный файл HomeScreen с одноименной composable функцией. Новый проект создавать не будем — сохраним настройки темы и стилей из прошлого урока.
В MainActivity оставлены только обертки темы и Scaffold. В качестве основного контейнера создан Box с центрированием контента, стандартными паддингами и растягиванием по всем осям.
Объяснение состояния и рекомпозиции начнем с простого чекбокса. Создаем функцию MainCheckBox, внутри объявляем Checkbox с двумя обязательными параметрами:
checked — отвечает за текущее состояние чекбокса,onCheckedChange — коллбэк, который срабатывает при переключении и принимает новое состояние (Boolean).В checked передаем true, в onCheckedChange — пустую лямбду с логом Log.i("!!!", "MainCheckBox $it"). Добавим модификатор для увеличения размера: .graphicsLayer(scaleX = 4f, scaleY = 4f).
@Composable @Preview(showBackground = true) fun MainCheckBox() { Checkbox( checked = true, onCheckedChange = { it: Boolean -> Log.i("!!!", "MainCheckBox $it") }, modifier = Modifier .graphicsLayer(scaleX = 4f, scaleY = 4f) ) }
В LogCat отбивается "MainCheckBox false", но визуально чекбокс не меняется — мы захардкодили значение true. При клике вызывается только onCheckedChange, и больше ничего.
Как оживить чекбокс?
Добавим переменную isChecked, передадим её в checked и будем присваивать ей новое значение в лямбде. Однако это тоже не работает. Дебаг показывает:
isChecked присваивается новое значение,Почему? Compose не знает, что нужно выполнить рекомпозицию при изменении обычной переменной. В декларативных фреймворках изменения состояния должны приводить к повторному вызову Composable функций — без этого UI не обновится.
Можно попробовать currentRecomposeScope.invalidate() — принудительно запустить рекомпозицию. Это частично работает: рекомпозиция запускается, но при перезапуске функции isChecked инициализируется заново, и чекбокс снова отображается активным.
Вынос переменной за пределы функции решает проблему, но это плохая практика: одно состояние будет общим для всех вызовов функции. Эта цепочка костылей наглядно показывает суть рекомпозиции — это повторный вызов функции с новыми данными.
Чтобы Compose отслеживал изменения и выполнял рекомпозицию автоматически, нужно использовать специальные механизмы управления состоянием — тогда можно избавиться и от scope, и от вызова invalidate.
Этим механизмом является MutableState. С помощью mutableStateOf создаём строго типизированный объект. MutableState автоматически отслеживает изменения — при присваивании нового значения isChecked сразу запускается рекомпозиция.
Чтобы получить или записать хранимое значение, используется свойство value:
var isChecked: MutableState<Boolean> = mutableStateOf<Boolean>(true) @Composable @Preview(showBackground = true) fun MainCheckBox() { Checkbox( checked = isChecked.value, onCheckedChange = { it: Boolean -> Log.i("!!!", "MainCheckBox $it") isChecked.value = it }, modifier = Modifier .graphicsLayer(scaleX = 4f, scaleY = 4f) ) }
Рекомпозиция происходит сразу в момент присваивания нового значения — то есть сразу после клика.
Unidirectional Data Flow (UDF) — однонаправленный поток данных. Взаимодействие с UI инициирует изменение состояния. MutableState хранит текущее состояние приложения. Когда состояние меняется, UI запускает рекомпозицию и обновляет представление. Это обеспечивает предсказуемость: мы всегда знаем, как состояние влияет на UI.
Вынесенное за пределы функции состояние неудобно: при нескольких вызовах MainCheckBox будет одно общее состояние. Если перенести стейт внутрь функции — появится ошибка: "Создание стейта без использования remember".
remember — это composable функция, которая обращается к памяти composable функции. При первой инициализации MutableState функция remember запоминает объект. При следующей рекомпозиции, когда выполняется строка создания стейта, remember возвращает уже существующий объект вместо создания нового. Это улучшает производительность и привязывает состояние к жизненному циклу конкретного вызова функции.
var isChecked: MutableState<Boolean> = remember { mutableStateOf<Boolean>(true) }
Если вызвать MainCheckBox дважды — каждый вызов создаёт свою независимую переменную isChecked. Каждый чекбокс хранит собственное состояние.
Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier .padding(innerPadding) .fillMaxSize(), ) { MainCheckBox() Spacer(modifier = Modifier.height(30.dp)) MainCheckBox() }
Используя делегат by, можно сократить запись: var isChecked: Boolean by remember { mutableStateOf(true) }. Делегат управляет получением и установкой значения, как если бы мы работали с обычной переменной — без необходимости обращаться к .value. Переменная должна быть объявлена как var.
Стоит добавить, что рекомпозиция производится только у тех элементов, которые действительно изменили своё состояние. Jetpack Compose использует концепцию "умных" обновлений: система знает, какие элементы UI зависят от конкретного состояния, и перерисовывает только их. Например, при изменении одного чекбокса обновляется только он. Это снижает нагрузку на процессор и сохраняет высокую отзывчивость даже при сложном интерфейсе.
В следующем уроке создадим текстовое поле TextField, OutlinedTextField, рассмотрим вопрос валидации email и ErrorState.