Compose images
Skill almasumdev/awesome-kotlin-android-agent-skills/.github/skills/ui/compose-images
Curated agent skills, conventions, and workflows for building Kotlin Android apps with AI coding agents.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
4.5 KB, 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.