AsyncImageにplaceHolderを表示したくない
画面遷移後にサーバから受け取った画像を表示したかった...のですが、AsyncImageにURLを丸投げすると、画像の読み込み中は何も表示されず、後から画像が反映されてしまいます。
もちろんplaceHolderを指定することもできますが、UI次第では後から画像が反映されると気持ち悪かったりするので、APIから画像のURLを取得 -> coilで画像のキャッシュを作成 -> 完了次第UIを表示という順で表示する方法で実装しました。
実装
coil-composeのバージョンは2.2.2です。
interface ImageCacheRepository {
suspend fun loadCache(urlList: List<String>)
}
class DefaultImageCacheRepository(
private val context: Context,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) : ImageCacheRepository {
override suspend fun loadCache(urlList: List<String>) {
withContext(dispatcher) {
val jobs = urlList.map { imageUrl ->
async {
val request = ImageRequest.Builder(context)
.data(imageUrl)
.allowHardware(false)
.size(Size.ORIGINAL)
.build()
context.imageLoader.enqueue(request)
}
}
jobs.awaitAll()
}
}
}
imageLoader.enqueue()を実行するとBitmapの作成を並列でやってくれます。(imageLoader.execute()だとスレッドを止めるので注意。)
あとはこのリポジトリにapplicationContextを入れて、サーバから受け取ったURLのリストをloadCache()に渡し、loadCache()の実行完了後にUIが描画されるように実装すればOK。AsyncImageにキャッシュを作成したURLを渡せば通信を挟むことなく表示してくれます。
一応、並列でリクエストを送るので、あまりにも要素数の多いリストを渡すとサーバに負荷をかける可能性があります。
キャッシュについて
coilのImageLoaderはデフォルトでメモリとディスクの両方にキャッシュを作成します。
class DefaultRequestOptions(
val interceptorDispatcher: CoroutineDispatcher = Dispatchers.Main.immediate,
val fetcherDispatcher: CoroutineDispatcher = Dispatchers.IO,
val decoderDispatcher: CoroutineDispatcher = Dispatchers.IO,
val transformationDispatcher: CoroutineDispatcher = Dispatchers.IO,
val transitionFactory: Transition.Factory = Transition.Factory.NONE,
val precision: Precision = Precision.AUTOMATIC,
val bitmapConfig: Bitmap.Config = DEFAULT_BITMAP_CONFIG,
val allowHardware: Boolean = true,
val allowRgb565: Boolean = false,
val placeholder: Drawable? = null,
val error: Drawable? = null,
val fallback: Drawable? = null,
val memoryCachePolicy: CachePolicy = CachePolicy.ENABLED,
val diskCachePolicy: CachePolicy = CachePolicy.ENABLED,
val networkCachePolicy: CachePolicy = CachePolicy.ENABLED,
)
こちらは用途に応じて .diskCachePolicy(CachePolicy.DISABLED)を追加してディスクキャッシュを作成しないようにしたり、不要になった時点でcontext.imageLoader.memoryCache?.clear()でメモリキャッシュを削除したりしても良いかもしれません。
参考