Заполнители и состояния ошибок
Корректно обрабатывайте загрузку и сбои
«Заполнители и состояния ошибок» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Загрузка изображений с Coil
- Заполнители и состояния ошибок
- Кэширование и производительность
- Воспроизведение аудио и видео