Compose images
Skill almasumdev/awesome-kotlin-android-agent-skills/.github/skills/ui/compose-images
Expert guidance on loading and displaying images in Compose using Coil 3, including placeholders, error fallbacks, caching, and memory tuning. Use this for any image-loading task.From its SKILL.md
npx -y skills add almasumdev/awesome-kotlin-android-agent-skills --skill compose-imagesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.5 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
Images in Compose with Coil 3
Instructions
Use Coil 3 (io.coil-kt.coil3:coil-compose). It is Kotlin-first, coroutine-native, supports multiplatform, and targets AsyncImagePainter directly.
1. Gradle Setup
dependencies {
implementation("io.coil-kt.coil3:coil-compose:3.0.4")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.4")
implementation("io.coil-kt.coil3:coil-gif:3.0.4") // animated GIFs
implementation("io.coil-kt.coil3:coil-svg:3.0.4") // SVG decoder
}
2. App-Wide ImageLoader
Configure a single ImageLoader in your Application and expose it to Coil:
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.crossfade(true)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, percent = 0.20) // 20% of available memory
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache").toOkioPath())
.maxSizeBytes(50L * 1024 * 1024) // 50 MB
.build()
}
.components {
add(OkHttpNetworkFetcherFactory(callFactory = { buildOkHttpClient() }))
}
.build()
}
3. AsyncImage Basics
AsyncImage(
model = article.coverUrl,
contentDescription = article.title,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16f / 9f)
.clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.img_placeholder),
error = painterResource(R.drawable.img_error),
)
Always set contentDescription (null only for purely decorative images). Always specify a fixed size or aspect ratio so Coil can downsample — this is the single biggest memory win.
4. Request with Full Builder
val request = remember(article.coverUrl) {
ImageRequest.Builder(ctx)
.data(article.coverUrl)
.crossfade(200)
.memoryCacheKey(article.coverUrl)
.diskCacheKey(article.coverUrl)
.size(Size.ORIGINAL) // or Size(width, height) for explicit resize
.build()
}
AsyncImage(model = request, contentDescription = article.title, modifier = Modifier.fillMaxWidth())
5. State-Aware UI with rememberAsyncImagePainter
val painter = rememberAsyncImagePainter(article.coverUrl)
val state = painter.state.collectAsState().value
Box(Modifier.aspectRatio(1f)) {
Image(painter = painter, contentDescription = null, modifier = Modifier.matchParentSize())
if (state is AsyncImagePainter.State.Loading) {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
}
6. Precaching
suspend fun precache(urls: List<String>, loader: ImageLoader, ctx: Context) {
urls.forEach { url ->
loader.execute(ImageRequest.Builder(ctx).data(url).build())
}
}
Call from a ViewModel during idle time or a WorkManager job for images you'll need next.
7. Memory Tuning & Anti-Patterns
- Do size-down to the actual display size. Downloading a 4000×3000 JPEG to a 96 dp avatar is the #1 OOM source.
- Do use
contentScale = ContentScale.Crop+.clip(CircleShape)for avatars. - Don't decode large images on the main dispatcher; Coil already handles this but avoid
BitmapFactory.decodeFilemanually. - Don't stuff images into
@Immutablestate holders; pass URLs (Strings) and let Coil manage bitmap lifecycle.
8. Animated Content
Enable the coil-gif component and use the standard AsyncImage — it plays automatically. For very large sprites or LOTTIE-style animation, prefer androidx.compose.animation.graphics or the Lottie Compose library.
Checklist
- Exactly one
ImageLoaderis configured application-wide. - Every
AsyncImagehas a bounded size or aspect ratio. -
contentDescriptionis meaningful, or explicitlynullfor decorative images. -
placeholderanderrorare provided for user-facing images. - Disk cache size is capped (typical 50 MB).
- No custom
BitmapFactorydecoding on the main thread.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.