Recomposition: когда и что перерисовывается

Разбираемся в главном механизме Compose — recomposition — и понимаем, что и когда перерисовывается.

Recomposition — повторный вызов composable-функций при изменении состояния, чтобы обновить интерфейс в соответствии с новыми данными.

Что запускает recomposition

Перерисовка случается только тогда, когда меняется наблюдаемое состояние (созданное через mutableStateOf), которое читается внутри composable. Compose умный: он перевыполняет не всё дерево, а лишь те функции, что зависят от изменившегося значения.

@Composable
fun Profile() {
    var likes by remember { mutableStateOf(0) }
    Column {
        Text(text = "Профиль пользователя")   // читает только статичный текст
        LikeButton(likes) { likes++ }          // зависит от likes
    }
}

Когда likes растёт, Compose перерисует LikeButton (он читает likes), но заголовок «Профиль пользователя» трогать не будет — он от likes не зависит. Это и есть ключевое отличие от перерисовки экрана целиком: Compose отслеживает чтения состояния на уровне отдельных функций и обновляет только затронутые ветки дерева. Поэтому даже на сложном экране изменение одного счётчика обычно стоит копейки.

Composable могут выполняться в любом порядке

Compose не гарантирует порядок и количество вызовов. Одна и та же функция может выполниться много раз, может — параллельно, может — пропуститься, если её входные данные не изменились. Поэтому composable должны быть идемпотентными: при тех же входных данных давать тот же результат, без неожиданных эффектов.

Что значит «без эффектов»

Нельзя в теле composable увеличивать внешний счётчик, писать в лог при каждом проходе или слать запрос в сеть. Эти действия повторятся непредсказуемое число раз. Для побочных эффектов есть отдельные инструменты — о них в шестом разделе.

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

Compose ведёт «слоты» — память, где хранятся результаты прошлой композиции. При recomposition он сравнивает новые параметры со старыми. Если параметры composable не изменились и функция помечена как пропускаемая (skippable), Compose пропускает её вызов и переиспользует прошлый результат. Так достигается эффективность: перерисовывается лишь то, что реально поменялось.

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

  • Класть в тело composable побочные эффекты (логи, запросы) — они срабатывают на каждой перерисовке.
  • Рассчитывать на конкретный порядок выполнения composable — его нет.
  • Думать, что recomposition перерисовывает весь экран целиком — обычно это узкая область.

Итог

  • Recomposition запускается при изменении читаемого composable наблюдаемого состояния.
  • Перерисовываются только зависящие функции, остальное пропускается.
  • Composable должны быть чистыми: без побочных эффектов в теле.
Проверьте себя
1. Что запускает recomposition?
AЛюбой клик по экрану
BИзменение наблюдаемого состояния, которое читается внутри composable
CПоворот экрана
DКаждый кадр анимации устройства
2. Почему нельзя класть побочные эффекты прямо в тело composable?
AЭто запрещено компилятором всегда
BТело может выполняться много раз и в любом порядке, эффекты повторятся непредсказуемо
CПобочные эффекты замедляют компиляцию
DОни выполнятся только один раз и потеряются