Состояние и рекомпозиция

Состояние в Compose — это данные, которые могут меняться со временем; при их изменении Compose автоматически перезапускает зависимые composable, и этот процесс называется рекомпозицией.
Суть: связка remember и mutableStateOf делает интерфейс живым — изменение состояния автоматически перерисовывает только те части UI, что от него зависят.

Статичный экран бесполезен — приложение должно реагировать на действия. В Compose реакция строится на состоянии. Чтобы Compose следил за изменением значения, его оборачивают в mutableStateOf, а чтобы значение пережило рекомпозицию — в remember. Когда состояние меняется, Compose заново вызывает зависимые composable и обновляет картинку.

import androidx.compose.runtime.*

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Нажато: $count раз")
    }
}

Здесь count — состояние. По клику оно увеличивается, Compose замечает изменение и перерисовывает кнопку с новым текстом. Никакого ручного findViewById и setText — обновление декларативно.

Что такое рекомпозиция

Рекомпозиция — это повторный вызов composable-функций при изменении состояния, от которого они зависят. Compose умён: он перерисовывает не весь экран, а только те функции, которые читают изменившееся состояние. Это ключ к производительности декларативного UI.

  Цикл состояния и UI

  Состояние (count = 0)
        |
        v
   Composable читает count --> рисует "Нажато: 0 раз"
        ^                                   |
        |  count++ (клик)                   |
        +-----------------------------------+
        |
        v
   РЕКОМПОЗИЦИЯ: перерисовка с count = 1

Зачем нужен remember

Без remember значение пересоздавалось бы при каждой рекомпозиции и счётчик всегда сбрасывался бы в ноль. remember сохраняет объект между рекомпозициями. Важный нюанс: remember не переживает поворот экрана. Чтобы значение сохранилось и при пересоздании Activity, используют rememberSaveable.

// переживёт поворот экрана
var name by rememberSaveable { mutableStateOf("") }

Как работает под капотом

Compose ведёт внутреннюю таблицу слотов — структуру, в которой запоминает результаты remember для каждой позиции в дереве. mutableStateOf создаёт наблюдаемое состояние: каждое чтение этого состояния внутри composable «подписывает» функцию на изменения. Когда значение меняется, Compose помечает подписанные функции как требующие рекомпозиции и перезапускает их в следующем кадре. Синтаксис by — это делегат, позволяющий читать и писать состояние как обычную переменную.

Частые ошибки

Забыть remember. Без него состояние сбрасывается на каждой рекомпозиции — счётчик не работает.

Менять обычную переменную вместо состояния. Изменение не-наблюдаемой переменной не вызывает рекомпозицию, и UI не обновляется.

Долгие операции прямо в теле composable. Тело может вызываться часто (на каждой рекомпозиции); тяжёлую работу выносят в side-effect или ViewModel.

Best practices

  • Изменяемые данные UI оборачивайте в remember { mutableStateOf(...) }.
  • Для переживания поворота используйте rememberSaveable.
  • Не выполняйте побочные эффекты прямо в теле composable.
  • Держите composable чистыми: один и тот же ввод — один и тот же UI.

Цикл «состояние меняется — UI пересобирается» удобно смоделировать на Python как простой счётчик с перерисовкой. Запустите врезку.

# Аналог состояния и рекомпозиции из Compose
def render(count):
    # как тело composable: UI зависит только от состояния
    return 'Кнопка [ Нажато: ' + str(count) + ' раз ]'

count = 0
print(render(count))          # начальное состояние
for _ in range(3):
    count += 1                # клик меняет состояние
    print(render(count))      # рекомпозиция: новая отрисовка

Попробуй сам ▶ — добавьте уменьшение счётчика или сброс в ноль и вызывайте render после каждого изменения. В Compose render вызывается автоматически при изменении состояния.

Закрепим главное

Зафиксируйте причинно-следственную связь: меняется наблюдаемое состояние — происходит рекомпозиция зависимых функций. Всё остальное в Compose вращается вокруг этого. Если UI не обновляется, почти всегда причина одна: вы изменили обычную переменную, а не состояние, и Compose просто не узнал об изменении. Привычка спрашивать «это состояние наблюдаемое?» снимает большинство ранних вопросов «почему экран не реагирует».

Второй ориентир — чистота composable. Тело функции может вызываться часто и в непредсказуемые моменты, поэтому в нём не место побочным эффектам и тяжёлой работе: запросам, записи в файл, изменению глобальных переменных. Composable должен быть функцией от состояния — те же входные данные дают тот же UI. Эффекты выносят в специальные конструкции и в ViewModel. Эта дисциплина делает перерисовку безопасной и предсказуемой, что мы и используем, поднимая состояние в следующих уроках.

Итог: состояние через mutableStateOf и remember делает Compose-интерфейс живым, а рекомпозиция эффективно обновляет только нужные части. Дальше мы разберём, как правильно организовать состояние между composable.

Проверьте себя
1. Что такое рекомпозиция в Jetpack Compose?
AПолная перезагрузка приложения
BПовторный вызов composable-функций, зависящих от изменившегося состояния, для обновления UI
CКомпиляция кода в байт-код
DСохранение данных в базу
2. Зачем оборачивать состояние в remember?
AЧтобы ускорить компиляцию
BЧтобы значение сохранялось между рекомпозициями и не сбрасывалось при каждой перерисовке
CЧтобы запретить изменение значения
DЧтобы значение пережило поворот экрана