Перейти к основному содержимому

Jetpack Compose — первый экран

Разработчику Архитектору

Jetpack Compose — первый экран

Где применяют Compose на Android

Jetpack Compose — способ строить интерфейс Android на Kotlin-функциях, а не в XML. Вы описываете "при таком состоянии экран выглядит так"; при изменении числа, текста или списка фреймворк перерисовывает только нужные части (recomposition).

Общий UI на нескольких платформах — Compose Multiplatform. Теория: экосистема Kotlin. Фоновая работа: корутины, Flow.


Словарь терминов

ТерминПростыми словами
ActivityОкно приложения на Android; точка входа экрана.
@ComposableФункция, которая рисует UI.
State (состояние)Данные, от которых зависит картинка (число счётчика, текст поля).
rememberСохранить состояние между перерисовками.
ViewModelОбъект, который переживает поворот экрана и хранит данные экрана.
StateFlowПоток "последнее значение состояния" для UI.
NavHostКонтейнер нескольких экранов и переходов между ними.

Что получится

Экран со счётчиком на Material3: remember для локального состояния и опционально ViewModel, чтобы счётчик не сбрасывался при повороте телефона.


Создание проекта

Android Studio: New Project → Empty Activity (или Empty Compose Activity).

Фрагмент app/build.gradle.kts:

Код ITЗагрузка примера кода…

Разбор:

  • buildFeatures { compose = true } включает поддержку Compose в Android-модуле.
  • Java 17 в compileOptions фиксирует целевой уровень байткода и API.
  • compose-bom синхронизирует версии всех артефактов Compose.
  • activity-compose соединяет жизненный цикл Activity с деревом Composable-функций.
  • lifecycle-viewmodel-compose добавляет удобную связку ViewModel и UI.

compose-bom согласует версии библиотек Compose. minSdk 24–26 — типичный минимум для новых проектов.


MainActivity — подключение Compose

Код ITЗагрузка примера кода…

ЭлементРоль
ComponentActivityБазовая Activity с поддержкой Compose.
setContent { }"Корень" UI на Compose вместо setContentView(R.layout...).
MaterialThemeЦвета и типографика Material.
SurfaceФон и отступы контента.

Состояние на экране

Код ITЗагрузка примера кода…


Разбор API

APIНазначение
@ComposableПомечает функцию UI; вызывается только из Composable или setContent.
remember { mutableStateOf(0) }Один раз создать состояние 0, хранить между recomposition.
byДелегат: писать count++, а не count.value++.
mutableStateOfПри изменении значения Compose планирует перерисовку.
ColumnВертикальная раскладка.
Modifier.fillMaxSize().padding(24.dp)Размер и отступы.

Без remember при каждой перерисовке count снова стал бы 0.


ViewModel — состояние переживает поворот

При повороте Activity пересоздаётся; remember внутри Composable без ViewModel тоже сбросится. Решение — вынести данные в ViewModel:

Код ITЗагрузка примера кода…

Экран:

Код ITЗагрузка примера кода…

СтрокаСмысл
viewModel()Получить или создать VM, привязанную к Activity / NavBackStackEntry.
collectAsState()Подписаться на StateFlow и получать значение для Composable.
vm::incrementСсылка на метод — удобная передача в onClick.

Загрузка данных с сети

Типичная связка: Ktor Client + viewModelScope:

Код ITЗагрузка примера кода…

В UI: CircularProgressIndicator, пока список пуст; ошибки — поле в UiState или Snackbar. Постоянное хранение на устройстве — Room.


Навигация между экранами

implementation("androidx.navigation:navigation-compose:2.7.7")

Код ITЗагрузка примера кода…

Разбор:

  • rememberNavController() создаёт и сохраняет контроллер навигации между recomposition.
  • NavHost описывает граф маршрутов и стартовый экран приложения.
  • composable("home") и composable("details") регистрируют отдельные экраны по route.
  • nav.navigate("details") добавляет новый экран в back stack.
  • nav.popBackStack() возвращает пользователя на предыдущий экран.

startDestination — первый экран. navigate / popBackStack — вперёд и назад.


Preview в Android Studio


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

@Preview(showBackground = true)
@Composable
fun CounterPreview() {
MaterialTheme {
CounterScreen()
}
}

Разбор:

  • @Preview говорит Android Studio, что Composable можно отрисовать без запуска приложения.
  • showBackground = true добавляет фон для удобной проверки контрастности и отступов.
  • Обёртка в MaterialTheme делает preview ближе к реальному виду экрана.
  • Отдельная preview-функция ускоряет разработку UI, особенно при мелких правках вёрстки.

Preview рисует Composable без запуска эмулятора — быстрая вёрстка.


Compose и XML

Новые экраны пишут на Compose. Старые View можно встроить через AndroidView и мигрировать постепенно. Для нового проекта выбирайте шаблон с Compose сразу.


Как мыслить состоянием в Compose

Стабильная модель для большинства экранов:

Категория состоянияГде хранитьПример
Эфемерное UI-состояниеremember / rememberSaveableоткрыт ли dropdown, текст фильтра
Состояние экранаViewModel + StateFlowсписок заметок, загрузка, ошибка
Данные приложениярепозиторий + БД/сетьпрофиль пользователя, каталог товаров

Это помогает избежать хаоса, когда часть данных "живет" в Composable, а часть в сервисах.


Разделение на UiState и UiEvent

data class NotesUiState(
val isLoading: Boolean = false,
val items: List<Note> = emptyList(),
val error: String? = null
)

sealed interface NotesUiEvent {
data class InputChanged(val value: String) : NotesUiEvent
data object AddClicked : NotesUiEvent
data class DeleteClicked(val id: Long) : NotesUiEvent
}

Разбор:

  • NotesUiState хранит полный снимок данных, по которому экран рисует текущее состояние.
  • isLoading отделяет реальную загрузку от ситуации "данных пока нет".
  • error задаёт явный канал для сообщения об ошибке, а не скрытых side effect.
  • sealed interface NotesUiEvent фиксирует закрытый набор пользовательских событий.
  • Такой контракт упрощает обработку: на каждый event есть понятная ветка логики.

Такой контракт упрощает рост экрана — добавляется новый элемент UI, вы явно дописываете состояние и событие, не ломая старое поведение.


Производительность и recomposition

Практические правила для списков:

  • Передавайте в Composable только нужные данные, а не "весь большой объект".
  • Используйте key в LazyColumn, как у вас уже сделано.
  • Тяжёлые вычисления выносите в remember(...) { ... } или в ViewModel.
  • Частые потоки ограничивайте distinctUntilChanged() на слое Flow.
val visibleItems by remember(notes) {
mutableStateOf(notes.filter { it.text.isNotBlank() })
}

Разбор:

  • remember(notes) пересчитывает выражение только когда меняется входной список notes.
  • filter { it.text.isNotBlank() } отбрасывает пустые элементы до рендера.
  • Делегат by делает чтение состояния лаконичным, без ручного .value.
  • Подход полезен для лёгких вычислений; для сложных сценариев подойдёт derivedStateOf.

Дополнительный сниппет — rememberSaveable для поворота

@Composable
fun NameInput() {
var name by rememberSaveable { mutableStateOf("") }

OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Имя") }
)
}

Разбор:

  • rememberSaveable сохраняет значение в SavedState, поэтому состояние переживает поворот.
  • В отличие от remember, значение не теряется при пересоздании Activity.
  • mutableStateOf("") создаёт наблюдаемое состояние, которое перерисовывает UI при изменении.
  • onValueChange поддерживает однонаправленный поток данных от ввода к состоянию.
  • Шаблон полезен для форм, фильтров и временного пользовательского ввода.

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

СимптомПричина
@Composable invocations can only happen...Вызов Composable из обычной функции
Счётчик сбрасывается при поворотеТолько remember, нет ViewModel / rememberSaveable
Preview пустойНет обёртки MaterialTheme
Красный Gradle syncНесовместимые версии Compose BOM и Kotlin

Что попробовать

  1. OutlinedTextField + LazyColumn со списком заметок.
  2. Второй экран через Navigation Compose.
  3. Вынести CounterScreen в commonMain CMP.

Связанные материалы