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

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:

android {
buildFeatures { compose = true }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

dependencies {
implementation(platform("androidx.compose:compose-bom:2024.06.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0")
}

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


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

package com.example.demo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
CounterScreen()
}
}
}
}
}
ЭлементРоль
ComponentActivityБазовая Activity с поддержкой Compose.
setContent { }«Корень» UI на Compose вместо setContentView(R.layout...).
MaterialThemeЦвета и типографика Material.
SurfaceФон и отступы контента.

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

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }

Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text("Счётчик: $count", style = MaterialTheme.typography.headlineMedium)
Button(onClick = { count++ }) {
Text("Увеличить")
}
OutlinedButton(onClick = { count = 0 }) {
Text("Сбросить")
}
}
}

Разбор 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:

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update

data class CounterUiState(val count: Int = 0)

class CounterViewModel : ViewModel() {
private val _state = MutableStateFlow(CounterUiState())
val state: StateFlow<CounterUiState> = _state.asStateFlow()

fun increment() = _state.update { it.copy(count = it.count + 1) }
fun reset() = _state.update { CounterUiState() }
}

Экран:

import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue

@Composable
fun CounterScreen(vm: CounterViewModel = viewModel()) {
val uiState by vm.state.collectAsState()

Column(Modifier.padding(24.dp)) {
Text("Счётчик: ${uiState.count}")
Button(onClick = vm::increment) { Text("+1") }
OutlinedButton(onClick = vm::reset) { Text("Сброс") }
}
}
СтрокаСмысл
viewModel()Получить или создать VM, привязанную к Activity / NavBackStackEntry.
collectAsState()Подписаться на StateFlow и получать значение для Composable.
vm::incrementСсылка на метод — удобная передача в onClick.

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

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

import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class NotesViewModel(private val api: NotesApi) : ViewModel() {
private val _state = MutableStateFlow<List<Note>>(emptyList())
val state = _state.asStateFlow()

init {
viewModelScope.launch {
_state.value = api.fetchNotes()
}
}
}

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


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

implementation("androidx.navigation:navigation-compose:2.7.7")
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

@Composable
fun AppNav() {
val nav = rememberNavController()
NavHost(navController = nav, startDestination = "home") {
composable("home") {
CounterScreen(onOpenDetails = { nav.navigate("details") })
}
composable("details") {
DetailsScreen(onBack = { nav.popBackStack() })
}
}
}

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


Preview в Android Studio

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

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

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


Compose и XML

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


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

СимптомПричина
@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.

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


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).