0Pricing
Android Academy · Урок

Заполнители и состояния ошибок

Корректно обрабатывайте загрузку и сбои

«Заполнители и состояния ошибок» — бесплатный урок Android Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.

Загрузка происходит не мгновенно

Сетевым изображениям требуется время. Пока идёт загрузка, интерфейс показывает... ничего, если не обработать это состояние. А иногда изображение вообще не загружается: URL недействителен, сервер недоступен или пользователь не подключён к сети.

Качественное приложение показывает изображение-заполнитель во время загрузки и резервное изображение при ошибке, а не пустое место или сбой. Coil упрощает оба варианта.

В этом уроке вы добавите изображения-заполнители, изображения для ошибок и собственный интерфейс для каждого состояния.

Простой заполнитель

Самый быстрый способ добавить заполнитель — передать drawable в AsyncImage через параметр placeholder, используя painterResource. Coil будет показывать его, пока не завершится загрузка настоящего изображения.

Используйте нейтральный и лёгкий drawable, например серую область или силуэт логотипа.

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import coil3.compose.AsyncImage

@Composable
fun ImageWithPlaceholder(url: String) {
    AsyncImage(
        model = url,
        contentDescription = null,
        placeholder = painterResource(R.drawable.placeholder_gray)
    )
}

Добавление изображения для ошибки

Параметр error показывает резервный drawable, если запрос завершается ошибкой. Есть также fallback — он используется специально, когда model равен null, то есть загружать вообще нечего.

Если задать все три параметра — placeholder, error и fallback, — вы покроете все визуальные состояния почти без кода.

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import coil3.compose.AsyncImage

@Composable
fun RobustImage(url: String?) {
    AsyncImage(
        model = url,
        contentDescription = null,
        placeholder = painterResource(R.drawable.placeholder_gray),
        error = painterResource(R.drawable.image_broken),
        fallback = painterResource(R.drawable.no_image)
    )
}

Заполнитель через ImageRequest

Эти состояния можно задать и непосредственно в ImageRequest. Это удобно, если вы создаёте запрос один раз и повторно используете его или одновременно задаёте другие параметры запроса.

Функции уровня запроса принимают идентификаторы ресурсов drawable.

import android.content.Context
import coil3.request.ImageRequest
import coil3.request.crossfade
import coil3.request.error
import coil3.request.placeholder

fun buildRequest(context: Context, url: String) =
    ImageRequest.Builder(context)
        .data(url)
        .crossfade(true)
        .placeholder(R.drawable.placeholder_gray)
        .error(R.drawable.image_broken)
        .build()

Собственный интерфейс с SubcomposeAsyncImage

Drawable подходят не всегда: иногда для каждого состояния нужен индикатор, эффект мерцания или текст. SubcomposeAsyncImage позволяет передавать настоящие компонуемые элементы для состояний loading, error и success.

Этот подход гибче, но немного тяжелее, поэтому для длинных списков лучше предпочитать drawable-заполнители, а субкомпозицию оставлять для крупных изображений.

import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import coil3.compose.SubcomposeAsyncImage

@Composable
fun HeroImage(url: String) {
    SubcomposeAsyncImage(
        model = url,
        contentDescription = "Hero",
        loading = { CircularProgressIndicator() },
        error = { Text("Could not load image") }
    )
}

Прямое чтение состояния

Для полного контроля используйте API rememberAsyncImagePainter и проверяйте его state. Состояние представляет собой запечатанный тип с вариантами Loading, Success, Error и Empty.

Это позволяет управлять собственной разметкой, анимациями или аналитикой в зависимости от результата.

import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter

@Composable
fun StateAwareImage(url: String) {
    val painter = rememberAsyncImagePainter(model = url)
    val state = painter.state.collectAsState().value

    when (state) {
        is AsyncImagePainter.State.Loading -> { /* show spinner */ }
        is AsyncImagePainter.State.Error -> { /* show error UI */ }
        else -> Image(painter = painter, contentDescription = null)
    }
}

Отображение заполнителя с мерцанием

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

Здесь во время загрузки мы используем SubcomposeAsyncImage с тонированной областью. Если нужен анимированный эффект, замените эту область библиотекой мерцания.

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import coil3.compose.SubcomposeAsyncImage

@Composable
fun ShimmerImage(url: String) {
    SubcomposeAsyncImage(
        model = url,
        contentDescription = null,
        loading = {
            androidx.compose.foundation.layout.Box(
                Modifier.fillMaxSize()
                    .background(MaterialTheme.colorScheme.surfaceVariant)
            )
        }
    )
}

Обработка пустой модели или модели со значением null

Что делать, если URL ещё нет, например у пользователя отсутствует фотография профиля? Передача null в качестве модели активирует drawable из fallback (или из error, если fallback не задан).

Это избавляет от необходимости отдельно обрабатывать null в собственном коде: позвольте Coil показать стандартный аватар.

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import coil3.compose.AsyncImage

@Composable
fun ProfilePicture(photoUrl: String?) {
    // If photoUrl is null, Coil shows the fallback default avatar.
    AsyncImage(
        model = photoUrl,
        contentDescription = "Profile picture",
        fallback = painterResource(R.drawable.default_avatar),
        error = painterResource(R.drawable.default_avatar)
    )
}

Отслеживание ошибок с помощью listener

Иногда при неудачной загрузке нужно выполнить действие в коде, например записать сведения или повторить попытку. ImageRequest.Builder принимает listener с обратными вызовами успешной загрузки и ошибки.

Старайтесь делать эти обратные вызовы короткими: они выполняются в главном потоке.

import android.content.Context
import android.util.Log
import coil3.request.ImageRequest

fun loggedRequest(context: Context, url: String) =
    ImageRequest.Builder(context)
        .data(url)
        .listener(
            onError = { _, result ->
                Log.e("Coil", "Load failed", result.throwable)
            },
            onSuccess = { _, _ ->
                Log.d("Coil", "Loaded $url")
            }
        )
        .build()

Тонирование и цветовые фильтры

Заполнители и drawable для ошибок часто лучше выглядят, если их цвет соответствует теме. К AsyncImage можно применить ColorFilter — это особенно удобно для монохромных резервных изображений в виде значков, которые должны подстраиваться под светлую и тёмную темы.

Применяйте фильтр только к нужному виду заполнителя или изображения ошибки; для настоящих фотографий обычно оставляют исходные цвета.

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import coil3.compose.AsyncImage

@Composable
fun TintedFallback(url: String?) {
    AsyncImage(
        model = url,
        contentDescription = null,
        error = painterResource(R.drawable.ic_image_placeholder),
        colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant)
    )
}

Устойчивый компонент изображения

Соберём повторно используемый компонент, который аккуратно обрабатывает все состояния: фон во время загрузки, резервное изображение при ошибке, плавное появление и обрезку. Добавьте его в список и больше не беспокойтесь о пустых местах.

import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade

@Composable
fun ResilientImage(url: String?, size: Int = 96) {
    val context = LocalContext.current
    AsyncImage(
        model = ImageRequest.Builder(context)
            .data(url)
            .crossfade(true)
            .build(),
        contentDescription = null,
        contentScale = ContentScale.Crop,
        placeholder = painterResource(R.drawable.placeholder_gray),
        error = painterResource(R.drawable.image_broken),
        fallback = painterResource(R.drawable.default_avatar),
        modifier = Modifier.size(size.dp)
    )
}

Быстрая проверка

Вы передаёте model = null в AsyncImage. Какой drawable Coil показывает по умолчанию?

Итоги

Вы научились изящно обрабатывать загрузку изображений. Главное:

  • placeholder отображается во время загрузки, error — при ошибке, а fallback — если модель равна null.
  • Их можно задать в AsyncImage или в ImageRequest.
  • SubcomposeAsyncImage позволяет использовать настоящие компонуемые элементы (индикатор загрузки, эффект мерцания) для каждого состояния.
  • rememberAsyncImagePainter предоставляет исходное state для полного контроля.
  • Используйте listener запроса, чтобы записывать результаты загрузки в журнал или реагировать на них.

Далее: кэширование и производительность, чтобы при повторной загрузке изображения появлялись мгновенно.

Часто задаваемые вопросы

Урок «Заполнители и состояния ошибок» бесплатный?

Да — полный текст урока «Заполнители и состояния ошибок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.

Чему я научусь в уроке «Заполнители и состояния ошибок»?

Корректно обрабатывайте загрузку и сбои Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Android Academy?

Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Заполнители и состояния ошибок»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Android Academy?

Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Загрузка изображений с Coil
  2. Заполнители и состояния ошибок
  3. Кэширование и производительность
  4. Воспроизведение аудио и видео
← Назад к Android Academy