Android: Coil, Glide and BlurHash Placeholders
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Baseline date: 2026-10-07. Coil 3.6.3 and Glide 5.0.9 are the latest releases on Maven Central on that date. The
mechanics of both libraries (the request pipeline, ImageRequest, transformations, preloading, the Views and RecyclerView
integrations) are in Image Loading, Caching and Transformations with Coil
and Glide. This page does not repeat them. It covers what a media backend adds: choosing the variant that matches the
screen, configuring the caches, and showing a BlurHash while the image loads. The backend side is in
responsive images and thumbnails and
the BlurHash algorithm.
Ask for the right size
An app downloads whatever URL it is given. Pick the variant from the API’s list (see the JSON in responsive images) that is at least as wide as the target size in pixels, and prefer AVIF or WebP when the API offers them. WebP is supported by Android since API 14 for lossy and API 18 for lossless and alpha. AVIF decoding is built into the platform from Android 12 (API 31). On older devices, request the WebP or JPEG variant. A small helper keeps that decision in one place:
data class Variant(val format: String, val width: Int, val url: String)
fun bestVariant(variants: List<Variant>, targetWidthPx: Int): Variant {
val formats = if (android.os.Build.VERSION.SDK_INT >= 31) listOf("avif", "webp", "jpeg") else listOf("webp", "jpeg")
return formats.firstNotNullOf { format ->
variants.filter { it.format == format }
.sortedBy { it.width }
.let { sorted -> sorted.firstOrNull { it.width >= targetWidthPx } ?: sorted.lastOrNull() }
}
}
Reference: Android supported media formats.
Coil 3
// build.gradle.kts
dependencies {
implementation("io.coil-kt.coil3:coil-compose:3.6.3")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.6.3") // network fetcher: pick one coil-network-* artifact
}
Coil 3 split the network layer into separate artifacts, so a coil-network-okhttp (or coil-network-ktor3) artifact is
required to load URLs. Configure the loader once in the Application, with explicit cache sizes:
import android.app.Application
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import coil3.request.crossfade
class App : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.memoryCache { MemoryCache.Builder().maxSizePercent(context, 0.25).build() }
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache"))
.maxSizeBytes(256L * 1024 * 1024)
.build()
}
.crossfade(true)
.build()
}
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
@Composable
fun Cover(url: String, blurhash: String?, modifier: Modifier = Modifier) {
val placeholder = rememberBlurHashPainter(blurhash) // defined below
AsyncImage(
model = ImageRequest.Builder(LocalContext.current).data(url).build(),
contentDescription = null,
placeholder = placeholder,
contentScale = ContentScale.Crop,
modifier = modifier,
)
}
Coil sizes the decoded bitmap to the composable’s size, so a 1280 pixel file shown in a 200 dp slot is not kept at full size
in memory. The disk cache stores the downloaded file. Coil also honours the server’s Cache-Control headers, which is
why immutable, versioned variant URLs from the CDN work well. References: Coil
getting started, image loaders and
network.
BlurHash as a placeholder
Two options. The woltapp Kotlin decoder is a single file to copy
into the project (BlurHashDecoder), and vanniktech/blurhash (0.3.0) is a
published Kotlin Multiplatform library, see
Kotlin Multiplatform and Flutter. With the woltapp
decoder:
import android.graphics.Bitmap
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
@Composable
fun rememberBlurHashPainter(hash: String?, width: Int = 32, height: Int = 32): Painter? =
remember(hash) {
hash?.let { BlurHashDecoder.decode(it, width, height) } // returns Bitmap?
?.let { BitmapPainter(it.asImageBitmap()) }
}
Decode at 32 by 32 pixels and let Compose scale it, as on the web. The decoder is fast, but do not decode inside a
list item on every recomposition: the remember(hash) above caches per item, and BlurHashDecoder keeps its own
cosine table cache. With Views and Glide, set the decoded BitmapDrawable as placeholder(...).
Glide 5
dependencies {
implementation("com.github.bumptech.glide:glide:5.0.9")
ksp("com.github.bumptech.glide:ksp:5.0.9")
implementation("com.github.bumptech.glide:compose:<version>") // Compose integration, see the Glide releases page
}
Glide.with(imageView)
.load(bestVariant(variants, imageView.width).url)
.diskCacheStrategy(DiskCacheStrategy.ALL) // cache the original file and the resized result
.placeholder(BitmapDrawable(imageView.resources, BlurHashDecoder.decode(hash, 32, 32)))
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView)
DiskCacheStrategy.ALL stores both the downloaded data and the transformed result. AUTOMATIC (the default) is a
good choice for remote images, since it stores the original for remote data and only the transformed version for local
data, and NONE disables disk caching. In Compose, Glide offers GlideImage in its Compose integration, which is
marked experimental, so opt in with @OptIn(ExperimentalGlideComposeApi::class) and check the release notes of the
Compose artifact. Use Coil for new Compose-only apps, and keep Glide where an existing Views and RecyclerView screen
already uses it. References: Glide caching and
Glide Compose integration.
Fresco
Fresco (from Meta) manages its own memory pool and supports progressive JPEG and animated images. It is the legacy choice and is still used in React Native on Android. For a new native app, Coil or Glide is simpler.
Video and uploads
Android video is covered in Media3 and compressing before an upload in device-side compression and uploads.