Android Academy · レッスン

プレースホルダーとエラー状態

読み込み中や失敗時も自然に表示します。

レッスン 2/413 ステップ

「プレースホルダーとエラー状態」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全4レッスンが含まれています。

読み込みはすぐには終わらない

ネットワーク上の画像の読み込みには時間がかかります。その間、何も処理しなければUIには何も表示されません。さらに、URLが壊れている、サーバーが停止している、ユーザーがオフラインであるなどの理由で、画像が届かないこともあります。

完成度の高いアプリでは、読み込み中にプレースホルダーを表示し、失敗したときはフォールバックを表示します。空白やクラッシュのままにはしません。Coilなら、どちらも簡単に実装できます。

このレッスンでは、プレースホルダー、エラー画像、状態ごとのカスタムUIを追加します。

シンプルなプレースホルダー

プレースホルダーを追加する最も簡単な方法は、painterResourceを使ってplaceholderパラメーターにドローアブルを渡すことです。Coilは実際の画像の読み込みが完了するまで、それを表示します。

灰色のボックスやロゴのシルエットなど、ニュートラルで軽量なドローアブルを使用してください。

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パラメーターを使うと、リクエストが失敗したときにフォールバック用のドローアブルを表示できます。また、fallbackはmodelがnullの場合(読み込むデータがまったくない場合)に使用します。

プレースホルダー、エラー、フォールバックの3つを設定すれば、ほとんどコードを書かずに、あらゆる表示状態に対応できます。

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自体にも設定できます。リクエストを一度作成して再利用する場合や、他のリクエストオプションも同時に指定したい場合に便利です。

リクエストレベルの関数には、ドローアブルリソースIDを渡します。

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でカスタムUIを作る

ドローアブルで十分な場合もありますが、状態ごとにスピナー、シマー、テキストなどを表示したいこともあります。SubcomposeAsyncImageを使うと、loading、error、successに実際のコンポーザブルを指定できます。

柔軟性が高い一方で少し負荷が大きいため、長いリストではドローアブルのプレースホルダーを優先し、ヒーロー画像などに限定してsubcomposeを使ってください。

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") }
    )
}

状態を直接読み取る

完全に制御したい場合は、rememberAsyncImagePainter APIを使い、そのstateを確認します。状態は、Loading、Success、Error、Emptyのケースを持つsealed型です。

これにより、結果に応じて独自のレイアウト、アニメーション、分析処理を制御できます。

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または空のModelを処理する

たとえばプロフィール画像のないユーザーのように、まだURLがない場合はどうすればよいでしょうか。nullをmodelとして渡すと、fallbackのドローアブルが表示されます(fallbackが設定されていない場合はerrorが表示されます)。

これにより、自分のコードで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()

Tintとカラーフィルター

プレースホルダーやエラー用のドローアブルは、テーマに合わせて色を付けると見栄えがよくなることがあります。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に渡すと、Coilはデフォルトでどのdrawableを表示しますか?

まとめ

画像の読み込みを適切に処理できるようにしました。重要なポイントは次のとおりです。

  • 読み込み中はplaceholder、失敗時はerror、モデルがnullの場合はfallbackが表示されます。
  • AsyncImageまたはImageRequestに設定できます。
  • SubcomposeAsyncImageを使うと、状態ごとに実際のコンポーザブル(スピナーやシマーなど)を指定できます。
  • rememberAsyncImagePainterを使うと、生のstateにアクセスして完全に制御できます。
  • リクエストのlistenerを使うと、読み込み結果をログに記録したり、それに応じて処理したりできます。

次はキャッシュとパフォーマンスです。同じ画像を2回目に読み込むとき、瞬時に表示できるようにします。

無料で開始

AI チューターと学ぶ Kotlin — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
36
レッスン
152

よくある質問

「プレースホルダーとエラー状態」レッスンは無料ですか?

はい。「プレースホルダーとエラー状態」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全4レッスンが含まれています。

「プレースホルダーとエラー状態」で何を学びますか?

読み込み中や失敗時も自然に表示します。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Android Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「プレースホルダーとエラー状態」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAndroid Academyレッスンでコードを書いて実行できますか?

はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Coilで画像を読み込む
  2. プレースホルダーとエラー状態
  3. キャッシュとパフォーマンス
  4. 音声と動画の再生
← Android Academyに戻る