Coil — What It Is, Key Concepts, and ImageLoader in Android

Author: IT Sectr Published: 2026-05-05 Reading time: 9 min

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 is an image loading library for Android that uses Kotlin Coroutines and Kotlin Serialization.
  • ImageLoader is the central component that manages requests, caching, and image transformations.
  • Memory Cache stores decoded bitmap images in RAM for instant access.
  • Disk Cache saves compressed files on storage for offline work and reduced traffic.
  • Jetpack Compose — the library supports AsyncImage and SubcomposeAsyncImage for declarative UI.

What Is Coil?

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.

  • APK Size — about 150 KB (versus 500 KB for Glide)
  • Minimum Android Version — API 21 (Lollipop)
  • Dependency — Kotlin Coroutines (built-in)

How Coil Works: ImageLoader and ImageRequest

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

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.

kotlin
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.

Request execution flow

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.

  • Memory Cache — checked first, key is URL + parameters
  • Disk Cache — checked on memory miss, key is MD5 of URL
  • HttpEngine — OkHttp by default, customizable via component

Core Components of Coil

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

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

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

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.

Caching Levels in Coil

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.

LevelStorage TypeLifetimeDefault Size
Memory CacheBitmap in RAMUntil LRU eviction25% of heap, from 32 MB
Disk CacheJPEG/WebP filesUntil limit exceeded250 MB
Http CacheOkHttp responsesPer Cache-Control headersDepends 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 Usage Examples in Kotlin

Coil provides several integration methods depending on the application architecture. Let’s look at three key scenarios with working code examples.

Loading into ImageView via extension

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.

kotlin
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.

Usage in Jetpack Compose

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.

kotlin
@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.

Custom Target for non-standard output

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.

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

Coil Comparison with Other Libraries

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.

CharacteristicCoilGlidePicasso
LanguageKotlin (100%)Java + KotlinJava
APK Size~150 KB~500 KB~120 KB
CoroutinesBuilt-inNo (callback)No (callback)
Jetpack ComposeNative supportVia accompanistThird-party
GIF/WebPYes (built-in)Yes (built-in)No
Google RecommendationYes (I/O 2023)YesNo

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.

Setting Up Coil in an Android Project

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.

kotlin
// 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.

kotlin
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

What is Coil and what is it used for?

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.

How is Coil different from Glide?

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.

How to add Coil to a Kotlin project?

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.

What image types does Coil support?

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+.

How to configure cache in Coil?

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

  • Coil is an image loading library for Kotlin with full coroutine and Jetpack Compose support.
  • ImageLoader manages requests, caching, and transformations, using two cache levels: Memory Cache and Disk Cache.
  • AsyncImage and SubcomposeAsyncImage provide Compose integration, supporting placeholder, error, and success states.
  • APK Size ~150 KB makes Coil one of the most compact image loading libraries on the market.
  • Transformations (RoundedCorners, CircleCrop, Blur) are built into the library and work with hardware acceleration.
  • Disk Cache provides offline access to previously loaded images with a configurable storage limit.
  • Coil is recommended by Google in official Jetpack Compose guides, confirming its status as a modern standard.

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.

Discuss the project

Read also