Coil is an image loading library for Android, written in Kotlin and built on coroutines. According to the official documentation, the library supports Memory Cache, Disk Cache, and hardware-accelerated transformations. Coil stands out with its minimal APK size (about 150 KB) and full compatibility with Jetpack Compose.
Key Takeaways
Coil (Coroutine Image Loader) is an image loading library for Android, entirely written in Kotlin and using coroutines for asynchronous operations. It provides a unified API for loading bitmap images from the network, resources, file system, and Content Provider, with automatic multi-level caching.
Unlike Glide and Picasso, Coil uses Kotlin Coroutines instead of callback chains, making the code more linear and predictable. All loading and decoding operations execute on background threads via the Dispatchers.IO dispatcher, with results delivered to the main thread without explicit switching.
Coil supports transformations (Round, Blur, Grayscale), transition animations, SVG and GIF, as well as custom Targets for non-standard display. According to Google I/O 2023, Coil is recommended in official Jetpack Compose tutorials alongside Glide.
ImageLoader is the main component of Coil, responsible for executing loading requests and managing the cache. Each instance holds references to MemoryCache, DiskCache, BitmapPool, and a coroutine pool. By default, a singleton created via Coil.imageLoader(context) is used.
ImageRequest is an object that describes a single image loading request: data source (URL, URI, Int resource), target ImageView or Target, transformations, caching settings, and placeholder. ImageRequest is built through a builder, ensuring flexibility and readability.
val request = ImageRequest.Builder(context)
.data("https://example.com/image.jpg")
.crossfade(true)
.size(512, 512)
.transformations(listOf(RoundedCornersTransformation(12f)))
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.target(imageView)
.build()
Once built, the ImageRequest is passed to ImageLoader via enqueue or execute. The enqueue method launches a coroutine and returns a Disposable, allowing cancellation when leaving the screen. The execute method is a suspend function that returns a Result directly.
ImageLoader sequentially checks MemoryCache, DiskCache, and only on a miss from both executes a network request via HttpEngine. After loading, the bytes are decoded into a Bitmap considering the target size, transformations are applied, the result is stored in both caches and passed to the Target.
Coil is built on a component architecture with the ability to replace any part via Dependency Injection. All components are registered in ImageLoaderFactory and passed to the ImageLoader constructor through the builder.
ImageLoader is the entry point for all loading operations. Each instance contains a coroutine pool, BitmapPool, MemoryCache, DiskCache, and a list of interceptors. By default, one global instance is created, but for unit testing, separate instances with isolated caches can be created.
MemoryCache is an in-memory LRU (Least Recently Used) cache that stores decoded Bitmap objects. The default maximum size is 25% of available application memory, but not less than 32 MB. The cache key is formed from URL + size + transformations, preventing outdated image retrieval.
DiskCache is a file-based cache for raw data (JPEG, PNG, WebP) and decoded metadata. It is located in the application’s cache directory and supports automatic cleanup when the limit is exceeded. Disk operations are performed via DiskCache.Builder with directory and maximum size configuration.
Coil implements a multi-level caching strategy that minimizes network requests and speeds up image display. Each level has its own purpose and data lifetime.
| Level | Storage Type | Lifetime | Default Size |
|---|---|---|---|
| Memory Cache | Bitmap in RAM | Until LRU eviction | 25% of heap, from 32 MB |
| Disk Cache | JPEG/WebP files | Until limit exceeded | 250 MB |
| Http Cache | OkHttp responses | Per Cache-Control headers | Depends on HTTP client |
Memory Cache provides instant access to already decoded Bitmaps. Disk Cache ensures the app works without a network (offline-first) after the first load. Http Cache at the OkHttp level handles conditional requests with ETag and If-Modified-Since.
Caching policies are configured per-request via CachePolicy with three values: ENABLED, READ_ONLY, WRITE_ONLY, DISABLED. For example, for user avatars, READ_ONLY can be set for Memory Cache and ENABLED for Disk Cache.
Coil provides several integration methods depending on the application architecture. Let’s look at three key scenarios with working code examples.
load is an extension function for ImageView, the simplest way to load an image in one line. The function accepts a URL, URI, Int resource, or File, along with all optional parameters through a lambda configurator.
imageView.load("https://example.com/photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
size(300, 300)
transformations(CircleCropTransformation())
}
The load method returns a Disposable, which can be cancelled in onDestroy or when reusing the View. This prevents memory leaks and unnecessary network requests during fast list scrolling.
AsyncImage is a composable function for loading images in declarative UI. It accepts any data source and three optional parameters for states: placeholder, error, and success.
@Composable
fun NetworkImage(url: String) {
AsyncImage(
model = url,
contentDescription = "network image",
placeholder = ColorPainter(Color.Gray),
error = ColorPainter(Color.Red)
)
}
SubcomposeAsyncImage is a more flexible version that allows customizing the display during loading through a content slot. This is useful for skeletons (shimmer) and progress bars.
If ImageView or AsyncImage are not suitable, you can implement a Target with a single onSuccess method that accepts a Bitmap. This is used for loading into Notification, RemoteViews, or OpenGL textures.
val target = object : BitmapTarget() {
override fun onSuccess(result: Bitmap) {
notificationRemoteView.setImageViewBitmap(R.id.icon, result)
}
}
imageLoader.enqueue(
ImageRequest.Builder(context)
.data(url)
.target(target)
.build()
)
The choice of image loading library depends on project requirements. Coil competes with Glide and Picasso, each having their strengths. A comparison of key characteristics is presented in the table.
| Characteristic | Coil | Glide | Picasso |
|---|---|---|---|
| Language | Kotlin (100%) | Java + Kotlin | Java |
| APK Size | ~150 KB | ~500 KB | ~120 KB |
| Coroutines | Built-in | No (callback) | No (callback) |
| Jetpack Compose | Native support | Via accompanist | Third-party |
| GIF/WebP | Yes (built-in) | Yes (built-in) | No |
| Google Recommendation | Yes (I/O 2023) | Yes | No |
For new projects on Kotlin and Jetpack Compose, Coil becomes the natural choice thanks to zero additional coroutine dependencies and minimal size. Glide remains preferred for complex scenarios with animations and video previews. Picasso falls short of both in functionality but wins in simplicity.
Adding Coil to an Android project is done via a Gradle dependency. After adding, the library automatically registers an ImageLoader via ContentProvider, so manual initialization in Application is not required. If customization is needed, a custom ImageLoader is created through the builder.
// build.gradle.kts (app module)
dependencies {
implementation("io.coil-kt:coil:2.6.0")
// For Jetpack Compose additionally:
implementation("io.coil-kt:coil-compose:2.6.0")
// For SVG support:
implementation("io.coil-kt:coil-svg:2.6.0")
// For GIF support:
implementation("io.coil-kt:coil-gif:2.6.0")
}
For customizing ImageLoader, ImageLoaderFactory is used — a singleton created in Application.onCreate. In the factory, you can configure cache limits, HTTP client, custom decoders, and logging. By default, Coil uses OkHttp with a ready connection pool.
class App : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(cacheDir.resolve("coil_cache"))
.maxSizeBytes(512 * 1024 * 1024)
.build()
}
.build()
}
}
Frequently Asked Questions
Coil is an image loading library for Android, written in Kotlin using coroutines. It is used for asynchronous loading, caching, and displaying bitmap images from the network, resources, or file system.
Coil is written in 100% Kotlin and uses coroutines instead of the callback mechanism in Glide. Coil has a smaller APK size (~150 KB vs ~500 KB) and native Jetpack Compose support via AsyncImage.
Add the dependency io.coil-kt:coil:2.6.0 to build.gradle.kts. For Jetpack Compose, also add io.coil-kt:coil-compose:2.6.0. The library automatically registers an ImageLoader via ContentProvider.
Coil supports JPEG, PNG, WebP, BMP, SVG (via the coil-svg module), and GIF (via the coil-gif module). AVIF and HEIF formats are supported through a custom decoder on devices with Android 10+.
Cache is configured via ImageLoader.Builder: memoryCache specifying the percentage of heap, diskCache with path and limit in bytes. Cache policies (ENABLED, DISABLED, READ_ONLY) are configured per-request via CachePolicy.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also