Императивный и декларативный UI

Этот урок объясняет, чем декларативный UI отличается от старого императивного и почему Compose меняет правила игры.

Декларативный UI — это подход, при котором вы описываете, как интерфейс должен выглядеть для текущего состояния, а фреймворк сам решает, что и когда перерисовать.

Как было раньше: императивный View

Долгие годы Android-разработчики верстали экраны в XML, а потом «оживляли» их в коде. Чтобы поменять текст, нужно было найти виджет по идентификатору и вручную выставить ему новое значение:

val textView = findViewById<TextView>(R.id.title)
textView.text = "Привет"
val button = findViewById<Button>(R.id.btn)
button.setOnClickListener {
    textView.text = "Нажато!"
}

Это императивный стиль: вы отдаёте пошаговые команды («найди виджет», «поставь текст», «повесь обработчик»). Проблема в том, что состояние живёт в двух местах — в ваших переменных и внутри самих виджетов. Их легко рассинхронизировать: забыли обновить один TextView — и на экране устаревшие данные.

Как стало: декларативный Compose

В Compose вы не дёргаете виджеты. Вы пишете функцию, которая по входным данным возвращает описание интерфейса. Поменялись данные — функция вызывается заново и рисует новую картинку:

@Composable
fun Greeting(name: String) {
    Text(text = "Привет, $name")
}

Здесь нет findViewById, нет ручного setText. Есть единственный источник истины — параметр name. Изменился он — Compose сам перерисует Text. Вам не нужно следить за синхронизацией.

Аналогия

Императивный подход — это инструкция роботу-маляру: «возьми кисть, обмакни в синюю краску, проведи слева направо». Декларативный — это фраза «стена должна быть синей». Как именно её покрасить, фреймворк решает сам.

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

Compose-функции не возвращают объекты View. При вызове они регистрируют в специальном дереве описание того, что нужно нарисовать. Это дерево называют композицией. Когда данные меняются, Compose сравнивает новое описание со старым и обновляет только те узлы, которые реально изменились. Этот процесс называется recomposition — мы подробно разберём его в третьем разделе.

Важно: вызов Greeting("Аня") не значит «нарисуй прямо сейчас». Это значит «опиши, что для имени Аня экран выглядит так». Когда и сколько раз функция выполнится — забота фреймворка.

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

  • Пытаться «достучаться» до виджета и поменять его вручную, как в старой системе. В Compose так нельзя — меняйте состояние, а не UI.
  • Думать, что composable-функция выполняется один раз. Она может вызываться много раз при каждом изменении данных.
  • Складывать логику с побочными эффектами (запись в файл, сетевой запрос) прямо в тело composable — она будет срабатывать на каждой перерисовке.

Итог

  • Императивный UI: вы вручную меняете виджеты, состояние дублируется и рассинхронизируется.
  • Декларативный UI: вы описываете картинку по данным, фреймворк сам обновляет экран.
  • В Compose единственный источник истины — состояние, а не сами виджеты.
Проверьте себя
1. В чём ключевая идея декларативного UI?
AВы вручную находите виджеты и меняете их свойства
BВы описываете, как UI выглядит для текущего состояния, а фреймворк перерисовывает сам
CИнтерфейс описывается только в XML
DСостояние хранится внутри каждого виджета отдельно
2. Что заменяет вызовы findViewById и setText в Compose?
AРучное обновление виджетов через id
BСостояние как единственный источник истины
CXML-атрибуты
DПрямой доступ к Canvas