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

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.