Первое приложение на Jetpack Compose

Jetpack Compose — это современный декларативный UI-тулкит, где интерфейс описывается обычными Kotlin-функциями с аннотацией Composable, а не XML-разметкой.
Суть: в Compose вы описываете, как выглядит экран при данном состоянии, а фреймворк сам обновляет картинку при изменении данных — это проще и надёжнее старого императивного подхода.

До Compose интерфейс верстали в XML, а потом в коде искали элементы по идентификаторам и вручную меняли их свойства. Это было многословно и легко рассинхронизировалось. Compose переворачивает подход: интерфейс — это функция от состояния. Вы пишете @Composable-функции, которые описывают, что показать, а при изменении данных фреймворк перерисовывает нужные части сам.

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

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

Composable-функция — это обычная функция Kotlin, помеченная @Composable. Она ничего не возвращает, а вместо этого вызывает другие composable-функции, описывая дерево интерфейса. Точка входа — setContent в Activity.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting(name = "мир")
        }
    }
}

Базовые компоненты и контейнеры

Самые частые composable: Text — текст, Button — кнопка, Image — картинка. Для расположения используются контейнеры: Column размещает детей по вертикали, Row — по горизонтали, Box накладывает их друг на друга.

@Composable
fun WelcomeScreen() {
    Column {
        Text("Добро пожаловать")
        Button(onClick = { /* действие */ }) {
            Text("Начать")
        }
    }
}

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

Когда вы вызываете composable-функции, Compose не рисует сразу пиксели. Он строит описание интерфейса в виде дерева и сравнивает его с предыдущим. Изменилось только текстовое поле — перерисуется только оно. Этот процесс называется композицией, а повторный вызов при изменении данных — рекомпозицией (её мы подробно разберём в следующем разделе). Аннотация @Preview позволяет увидеть composable прямо в Android Studio без запуска на устройстве.

import androidx.compose.ui.tooling.preview.Preview

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    Greeting(name = "превью")
}
  Дерево Composable

  WelcomeScreen
     |
     +-- Column
          |
          +-- Text("Добро пожаловать")
          +-- Button
               |
               +-- Text("Начать")

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

Забыть аннотацию Composable. Без неё функция не может вызывать другие composable и не встроится в дерево интерфейса.

Возвращать значение из composable. Composable описывает UI через вызовы, а не возвращает виджеты; ничего возвращать не нужно.

Класть всё в одну гигантскую функцию. Разбивайте экран на мелкие переиспользуемые composable — так читаемее и эффективнее перерисовка.

Best practices

  • Делайте composable маленькими и сфокусированными на одной задаче.
  • Снабжайте ключевые composable превью через @Preview.
  • Передавайте данные параметрами, а не тяните их из глобального состояния.
  • Именуйте composable существительными с заглавной буквы (Greeting, WelcomeScreen).

Идею «UI как функция от состояния» удобно прочувствовать на Python: одна функция строит описание экрана из данных. Запустите врезку.

# Аналог 'UI как функция от состояния' из Compose
def greeting(name):
    return 'Text("Привет, ' + name + '!")'

def welcome_screen(user):
    lines = ['Column {']
    lines.append('  ' + greeting(user))
    lines.append('  Button { Text("Начать") }')
    lines.append('}')
    return '\n'.join(lines)

print(welcome_screen('мир'))
print('---')
print(welcome_screen('Аня'))

Попробуй сам ▶ — меняйте имя пользователя и смотрите, как меняется описание экрана. В Compose ровно так же: меняются данные — пересобирается описание UI.

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

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

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

Итог: Compose описывает интерфейс декларативно через @Composable-функции, базовые компоненты и контейнеры Column/Row/Box. Это первый шаг; дальше мы научимся делать интерфейс по-настоящему живым с помощью состояния.

Проверьте себя
1. Чем декларативный подход Compose отличается от старого XML-подхода?
AВ Compose интерфейс описывается в XML быстрее
BВ Compose вы описываете UI как функцию от состояния, и фреймворк сам перерисовывает изменившиеся части
CCompose не поддерживает кнопки и текст
DCompose работает только на старых версиях Android
2. Что делает аннотация @Preview?
AПубликует приложение в магазине
BПозволяет увидеть composable прямо в Android Studio без запуска на устройстве
CУскоряет компиляцию
DДелает функцию приватной