Перейти к содержанию
Educora
Продвинутый22 мин8 / 14

Sealed-классы, корутины и Jetpack Compose

Познакомься с тремя ключевыми инструментами современного Android-приложения: описывай состояния sealed-классами, выполняй работу в корутинах, не замораживая экран, и строй интерфейс на Jetpack Compose.

Проверь себя
В этом уроке ты узнаешь
  • Создавать sealed-класс и обрабатывать его в when без else
  • Объяснять роль suspend, launch, async/await и delay
  • Читать простую @Composable-функцию и понимать, как экран обновляется при изменении состояния

Ты открываешь мессенджер: сначала крутится индикатор загрузки, потом появляются сообщения, а если нет интернета — уведомление об ошибке. При этом экран не зависает — можно листать и нажимать кнопки. За этим стоят три возможности Kotlin: sealed-классы, описывающие состояния, корутины, выполняющие работу в фоне, и Jetpack Compose, который рисует экран. Этот урок — твой первый шаг в мир Android.

Sealed-классы: закрытое семейство

Все дочерние классы sealed («запечатанного») класса известны заранее и объявлены в том же пакете — добавить новый наследник где-то ещё нельзя. Это идеально для описания фиксированного набора вариантов вроде «загрузка / успех / ошибка». Каждый вариант может нести свои данные: причину ошибки, результат успеха. Для варианта без данных используют data object.

Kotlin
sealed class UiState {
    data object Loading : UiState()
    data class Success(val messages: Int) : UiState()
    data class Error(val reason: String) : UiState()
}

fun render(state: UiState): String = when (state) {
    UiState.Loading -> "Loading..."
    is UiState.Success -> "You have ${state.messages} new messages"
    is UiState.Error -> "Error: ${state.reason}"
}

fun main() {
    val states = listOf(UiState.Loading, UiState.Success(5), UiState.Error("No internet"))
    states.forEach { println(render(it)) }
    println(UiState.Success(5))
}
Ожидаемый результат
Loading...
You have 5 new messages
Error: No internet
Success(messages=5)

Обрати внимание: в выражении when нет ветки else. Компилятор знает, что у UiState всего три варианта, и проверяет, что обработаны все. А в ветке is UiState.Success работает умное приведение типа — state.messages доступно напрямую.

Корутины: ждать, не замораживая

В Android-приложении экран рисует главный поток (main thread). Загрузка данных из интернета может занять несколько секунд; если всё это время главный поток будет ждать, экран зависнет. Корутины решают эту проблему: корутина может приостановиться (suspend) посреди работы, освободить поток для других задач и продолжить, когда придёт ответ. Корутины очень лёгкие — одновременно могут работать тысячи.

  • suspend fun — функция, которая может приостанавливаться. Её можно вызвать только из корутины или из другой suspend-функции.
  • delay(1000) приостанавливает корутину на 1 секунду, но не блокирует поток.
  • launch { } запускает новую корутину, результат которой не ждут («запустил и забыл»).
  • async { } запускает корутину с результатом; результат получают через await().
  • runBlocking { } связывает обычную функцию main с миром корутин; он нужен только для примеров и тестов.
Kotlin
import kotlinx.coroutines.*

suspend fun fetchTemperature(city: String): Int {
    delay(1000)
    return if (city == "Baku") 24 else 19
}

fun main() = runBlocking {
    println("Loading...")
    launch {
        delay(500)
        println("Half a second passed")
    }
    val baku = async { fetchTemperature("Baku") }
    val ganja = async { fetchTemperature("Ganja") }
    println("Baku: ${baku.await()}, Ganja: ${ganja.await()}")
    println("Done")
}
Ожидаемый результат
Loading...
Half a second passed
Baku: 24, Ganja: 19
Done
Нужна библиотека kotlinx-coroutines-core. Два запроса выполняются параллельно, поэтому программа работает около 1 секунды, а не 2.

Вкус Android: Jetpack Compose

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

Kotlin
@Composable
fun Greeting(name: String) {
    var count by remember { mutableIntStateOf(0) }

    Column(modifier = Modifier.padding(16.dp)) {
        Text(text = "Hello, $name!")
        Button(onClick = { count++ }) {
            Text("Clicked $count times")
        }
    }
}

@Preview
@Composable
fun GreetingPreview() {
    Greeting("Aysel")
}
Результат: надпись «Hello, Aysel!» и кнопка под ней; каждое нажатие увеличивает число на кнопке на 1.
  • @Composable говорит, что функция описывает часть интерфейса. Такие имена пишут с заглавной буквы: Greeting.
  • remember { mutableIntStateOf(0) } создаёт состояние (state): Compose запоминает его значение и следит за ним.
  • Column располагает элементы вертикально, а Modifier.padding(16.dp) добавляет отступы по краям.
  • Button(onClick = { count++ }) { ... } — знакомая картина: две лямбды! Одна выполняется при нажатии, вторая (последняя лямбда) описывает содержимое кнопки.
  • @Preview позволяет Android Studio показать превью экрана без запуска приложения на телефоне.

Где же магия? При нажатии на кнопку count меняется. Compose замечает это и снова вызывает функцию Greeting — это называется рекомпозицией (recomposition) — и обновляет только изменившуюся часть экрана. Ты не обновляешь экран вручную, а просто меняешь состояние. Здесь сходятся все прошлые уроки: функции, лямбды, строковые шаблоны и состояния с when.

Главное

  • Все варианты sealed-класса известны, поэтому when может охватить их полностью без else.
  • Корутина может приостановиться, не блокируя поток; suspend-функции вызываются только из корутин.
  • launch запускает работу без результата, а async даёт результат, который получают через await().
  • В Compose интерфейс описывается @Composable-функциями; при изменении состояния экран обновляется автоматически (рекомпозиция).

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Почему в when по sealed-классу не нужен else?