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 |
Что попробовать
OutlinedTextField+LazyColumnсо списком заметок.- Второй экран через Navigation Compose.
- Вынести
CounterScreenвcommonMainCMP.
Связанные материалы
- Мобильные приложения на Kotlin
- Kotlin — KotlinMobileApp
- Compose Multiplatform
- Ktor Client
- Flow
- Room + ViewModel
- Тестирование