# Technical Documentation: coil-kt/coil

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/coil-kt/coil)

### Overview (README.md)

An image loading library for [Android](https://www.android.com/) and [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/). Coil is:

- **Fast**: Coil performs a number of optimizations including memory and disk caching, downsampling the image, automatically pausing/cancelling requests, and more.
- **Lightweight**: Coil only depends on Kotlin, Coroutines, and Okio and works seamlessly with Google's R8 code shrinker.
- **Easy to use**: Coil's API leverages Kotlin's language features for simplicity and minimal boilerplate.
- **Modern**: Coil is Kotlin-first and interoperates with modern libraries including Compose, Coroutines, Okio, OkHttp, and Ktor.

Coil is an acronym for: **Co**routine **I**mage **L**oader.

Translations: [日本語](README-ja.md), [한국어](README-ko.md), [Русский](README-ru.md), [Svenska](README-sv.md), [Türkçe](README-tr.md), [中文](README-zh.md), [پارسی](README-fa.md), [O'zbekcha](README-uz.md)

## Quick Start

Import the Compose library and a [networking library](https://coil-kt.github.io/coil/network/):

```kotlin
implementation("io.coil-kt.coil3:coil-compose:3.6.3")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.6.3")
```

To load an image, use the `AsyncImage` composable:

```kotlin
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null,
)
```

Check out Coil's [full documentation here](https://coil-kt.github.io/coil/getting_started/).

## License

    Copyright 2026 Coil Contributors

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       https://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

---

### Code Of Conduct (`docs/code_of_conduct.md`)

# Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
  advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at colin at colinwhite.me. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

---

### Faq (`docs/faq.md`)

# FAQ

Have a question that isn't part of the FAQ? Check [StackOverflow](https://stackoverflow.com/questions/tagged/coil) with the tag #coil or search [Github discussions](https://github.com/coil-kt/coil/discussions).

## Can Coil be used with Java projects or mixed Kotlin/Java projects?

Yes! [Read here](java_compatibility.md).

## How do I preload an image?

Launch an image request with no target:

```kotlin
val request = ImageRequest.Builder(context)
    .data("https://example.com/image.jpg")
    .build()
imageLoader.enqueue(request)
```

That will preload the image and save it to the disk and memory caches.

If you only want to preload to the disk cache you can skip decoding and saving to the memory cache like so:

```kotlin
val request = ImageRequest.Builder(context)
    .data("https://example.com/image.jpg")
    // Disables writing to the memory cache.
    .memoryCachePolicy(CachePolicy.DISABLED)
    // Skips the decode step so we don't waste time/memory decoding the image into memory.
    .decoderFactory(BlackholeDecoder.Factory())
    .build()
imageLoader.enqueue(request)
```

## How do I enable logging?

Set `logger(DebugLogger())` when [constructing your `ImageLoader`](getting_started.md#configuring-the-singleton-imageloader).

!!! Note
    `DebugLogger` should only be used in debug builds.

## How do I target Java 8 or Java 11?

Coil requires [Java 8 bytecode](https://developer.android.com/studio/write/java8-support). This is enabled by default on the Android Gradle Plugin `4.2.0` and later and the Kotlin Gradle Plugin `1.5.0` and later. If you're using older versions of those plugins add the following to your Gradle build script:

```kotlin
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
```

As of Coil `3.2.0`, Java 11 bytecode is required for `coil-compose` and `coil-compose-core`:

```kotlin
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}
```

## Why do I get a Skiko version warning with Compose Multiplatform?

Compose Multiplatform will print a warning like this if Coil's Skiko dependency is older than Compose Multiplatform's:

```text
w: Skiko dependencies' versions are incompatible.
```

Skiko does not guarantee binary compatibility between milestone versions because it wraps Skia, whose milestone versions can introduce breaking changes. This warning should not be assumed safe to ignore. Coil releases track Compose Multiplatform **stable** releases and their Skiko versions, so if you encounter this warning, first update Coil to the latest version.

**NOTE**: Coil doesn't release new versions only to match the Skiko versions used by **alpha** and **beta** Compose Multiplatform releases. However, if there is a binary compatibility issue that requires Coil to depend on a newer Skiko version, Coil will publish an **alpha** release with the updated dependency.

If you've updated Coil to the latest version and the warning is still present you can suppress the warning like so:

**Compose Multiplatform >= 1.13.0**

Add the following to the `build.gradle.kts` file of each module that applies the Compose Multiplatform plugin:

```kotlin
compose {
    dependencyCompatibility {
        exclude("io.coil-kt.coil3")
    }
}
```

This disables the compatibility check only for Coil.

**Older Compose Multiplatform versions:**

Add the following to your `gradle.properties` file:

```properties
org.jetbrains.compose.library.compatibility.check.disable=true
```

This disables all Compose Multiplatform library compatibility checks for all libraries, including Coil.

## How do I get development snapshots?

Add the snapshots repository to your list of repositories:

Gradle (`.gradle`):

```groovy
allprojects {
    repositories {
        maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
    }
}
```

Gradle Kotlin DSL (`.gradle.kts`):

```kotlin
allprojects {
    repositories {
        maven("https://central.sonatype.com/repository/maven-snapshots/")
    }
}
```

Then depend on the same artifacts with [the latest snapshot version](https://github.com/coil-kt/coil/blob/main/gradle.properties#L34).

!!! Note
    Snapshots are deployed for each new commit on `main` that passes CI. They can potentially contain breaking changes or may be unstable. Use at your own risk.

## How to I use Proguard with Coil?

To use Proguard with Coil, [add these Proguard rules to your config](https://github.com/coil-kt/coil/blob/main/coil-core/src/jvmMain/resources/META-INF/proguard/proguard-rules.pro).

You may also need to add custom rules for Ktor, OkHttp, and Coroutines.

!!! Note
    **You do not need to add any custom rules for Coil if you use R8**, which is the default code shrinker on Android. The rules are added automatically.

---

### Getting Started (`docs/getting_started.md`)

# Getting Started

## Compose UI

A typical Compose UI project will want to import:

```kotlin
implementation("io.coil-kt.coil3:coil-compose:3.6.3")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.6.3")
```

After that's imported you can load images from the network using `AsyncImage`:

```kotlin
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null,
)
```

!!! Note
    If you use Compose Multiplatform, you'll need to use Ktor instead of OkHttp. See [here](network.md#ktor-network-engines) for how to do that.

## Android Views

If you use Android Views instead of Compose UI import:

```kotlin
implementation("io.coil-kt.coil3:coil:3.6.3")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.6.3")
```

After that's imported you can load images from the network using the `ImageView.load` extension function:

```kotlin
imageView.load("https://example.com/image.jpg")
```

## Configuring the singleton ImageLoader

By default, Coil includes a singleton `ImageLoader`. The `ImageLoader` executes incoming `ImageRequest`s by fetching, decoding, caching, and returning the result. You don't need to configure your `ImageLoader`; if you don't Coil will create the singleton `ImageLoader` with the default configuration.

You can configure it a number of ways (**choose only one**):

- Call `setSingletonImageLoaderFactory` near the entrypoint to your app (the root `@Composable` of your app). **This works best for Compose Multiplatform apps.**

```kotlin
setSingletonImageLoaderFactory { context ->
    ImageLoader.Builder(context)
        .crossfade(true)
        .build()
}
```

- Implement `SingletonImageLoader.Factory` on your [`Application`](https://developer.android.com/reference/android/app/Application) in Android. **This works best for Android apps.**

```kotlin
class CustomApplication : Application(), SingletonImageLoader.Factory {
    override fun newImageLoader(context: Context): ImageLoader {
        return ImageLoader.Builder(context)
            .crossfade(true)
            .build()
    }
}
```

- Call `SingletonImageLoader.setSafe` near the entrypoint to your app (e.g. in `Application.onCreate` on Android). This is the most flexible.

```kotlin
SingletonImageLoader.setSafe { context ->
    ImageLoader.Builder(context)
        .crossfade(true)
        .build()
}
```

!!! Note
    If you are writing a library that depends on Coil you should NOT get/set the singleton `ImageLoader`. Instead, you should depend on `io.coil-kt.coil3:coil-core`, create your own `ImageLoader`, and pass it around manually. If you set the singleton `ImageLoader` in your library you could be overwriting the `ImageLoader` set by the app using your library if they also use Coil.

## Images

To support multiplatform rendering, Coil 3.x uses a custom `coil3.Image` class. It replaces Android's `Drawable`, but is fully interoperable with it:

```kotlin
val drawable = image.asDrawable(resources)
val image = drawable.asImage()
```

Coil also defines a `coil3.Bitmap` class, which is a type alias for `android.graphics.Bitmap` on Android or `org.jetbrains.skia.Bitmap` on non-Android platforms:

```kotlin
val bitmap = image.toBitmap()
val image = bitmap.asImage()
```

It's also interoperable with Compose UI's `Painter` class. This extension function requires importing the `coil-compose-core` artifact:

```kotlin
val painter = image.asPainter()
```

!!! Note
    `Painter`s can't be converted to `Image`s as painters can only be rendered inside a composition whereas `Image`s must be able to be rendered on any `Canvas`.

## Artifacts

Here's a list of the main artifacts Coil has published to `mavenCentral()`:

* `io.coil-kt.coil3:coil`: The default artifact which depends on `io.coil-kt.coil3:coil-core`. It includes a singleton `ImageLoader` and related extension functions.
* `io.coil-kt.coil3:coil-core`: A subset of `io.coil-kt.coil3:coil` which **does not** include the singleton `ImageLoader` and related extension functions.
* `io.coil-kt.coil3:coil-compose`: The default [Compose UI](https://www.jetbrains.com/compose-multiplatform/) artifact which depends on `io.coil-kt.coil3:coil` and `io.coil-kt.coil3:coil-compose-core`. It includes overloads for `AsyncImage`, `rememberAsyncImagePainter`, and `SubcomposeAsyncImage` that use the singleton `ImageLoader`.
* `io.coil-kt.coil3:coil-compose-core`: A subset of `io.coil-kt.coil3:coil-compose` which does not include functions that depend on the singleton `ImageLoader`.
* `io.coil-kt.coil3:coil-network-okhttp`: Includes support for fetching images from the network using [OkHttp](https://github.com/square/okhttp).
* `io.coil-kt.coil3:coil-network-ktor2`: Includes support for fetching images from the network using [Ktor 2](https://github.com/ktorio/ktor).
* `io.coil-kt.coil3:coil-network-ktor3`: Includes support for fetching images from the network using [Ktor 3](https://github.com/ktorio/ktor).
* `io.coil-kt.coil3:coil-network-cache-control`: Includes support for respecting [`Cache-Control` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) when fetching images from the network.
* `io.coil-kt.coil3:coil-gif`: Includes two [decoders](/coil/api/coil-core/coil3.decode/-decoder) to support decoding GIFs. See [GIFs](gifs.md) for more details.
* `io.coil-kt.coil3:coil-svg`: Includes a [decoder](/coil/api/coil-core/coil3.decode/-decoder) to support decoding SVGs. See [SVGs](svgs.md) for more details.
* `io.coil-kt.coil3:coil-video`: Includes a [decoder](/coil/api/coil-core/coil3.decode/-decoder) to support decoding frames from [any of Android's supported video formats](https://developer.android.com/guide/topics/media/media-formats#video-codecs). See [videos](videos.md) for more details.
* `io.coil-kt.coil3:coil-test`: Includes classes to support testing. See [testing](testing.md) for more details.
* `io.coil-kt.coil3:coil-bom`: Includes a [bill of materials](https://docs.gradle.org/7.2/userguide/platforms.html#sub:bom_import). Importing `coil-bom` allows you to depend on other Coil artifacts without specifying a version.

---

### Image Loaders (`docs/image_loaders.md`)

# Image Loaders

`ImageLoader`s are [service objects](https://publicobject.com/2019/06/10/value-objects-service-objects-and-glue/) that execute [`ImageRequest`](image_requests.md)s. They handle caching, data fetching, image decoding, request management, memory management, and more.

Coil performs best when you create a single `ImageLoader` and share it throughout your app. This is because each `ImageLoader` has its own memory cache, disk cache, and `OkHttpClient`.

## Singleton

The default `io.coil-kt.coil3:coil` artifact comes with a singleton `ImageLoader`. Coil creates this `ImageLoader` lazily. It can be configured a number of ways:

```kotlin
// The setSafe method ensures that it won't overwrite an
// existing image loader that's been created.
SingletonImageLoader.setSafe {
    ImageLoader.Builder(context)
        .crossfade(true)
        .build()
}

// An alias of SingletonImageLoader.setSafe that's useful for
// Compose Multiplatform apps.
setSingletonImageLoaderFactory { context ->
    ImageLoader.Builder(context)
        .crossfade(true)
        .build()
}

// Should only be used in tests. If you call this method
// multiple times it will create multiple image loaders.
SingletonImageLoader.setUnsafe {
    ImageLoader.Builder(context)
        .crossfade(true)
        .build()
}

// On Android you can implement SingletonImageLoader.Factory on your
// Application class to have it create the singleton image loader.
class CustomApplication : SingletonImageLoader.Factory {
    override fun newImageLoader(context: Context): ImageLoader {
        return ImageLoader.Builder(context)
            .crossfade(true)
            .build()
    }
}
```

**In all cases ensure the above methods should be invoked as soon as possible when your app starts (i.e. inside `Application.onCreate` or inside `MainActivity.onCreate` if your app is only a single `Activity`.)**

## Dependency injection

If you have a larger app or want to manage your own `ImageLoaders` you can depend on `io.coil-kt.coil3:coil-core` instead of `io.coil-kt.coil3:coil`.

This route makes scoping the lifecycle of a fake `ImageLoader` much easier and will overall make testing easier.

## Caching

Each `ImageLoader` keeps a memory cache of recently decoded `Bitmap`s as well as a disk cache for any images loaded from the Internet. Both can be configured when creating an `ImageLoader`:

```kotlin
val imageLoader = ImageLoader.Builder(context)
    .memoryCache {
        MemoryCache.Builder()
            .maxSizePercent(context, 0.25)
            .build()
    }
    .diskCache {
        DiskCache.Builder()
            .directory(context.cacheDir.resolve("image_cache"))
            .maxSizePercent(0.02)
            .build()
    }
    .build()
```

You can access items in the memory and disk caches using their keys, which are returned in an `ImageResult` after an image is loaded. The `ImageResult` is returned by `ImageLoader.execute` or in `ImageRequest.Listener.onSuccess` and `ImageRequest.Listener.onError`.

---

### Image Pipeline (`docs/image_pipeline.md`)

# Extending the Image Pipeline

Android supports many [image formats](https://developer.android.com/guide/topics/media/media-formats#image-formats) out of the box, however there are also plenty of formats it does not (e.g. GIF, SVG, MP4, etc.)

Fortunately, [ImageLoader](image_loaders.md)s support pluggable components to add new cache layers, new data types, new fetching behavior, new image encodings, or otherwise overwrite the base image loading behavior. Coil's image pipeline consists of five main parts that are executed in the following order: [Interceptors](/coil/api/coil-core/coil3.intercept/-interceptor), [Mappers](/coil/api/coil-core/coil3.map/-mapper), [Keyers](/coil/api/coil-core/coil3.key/-keyer), [Fetchers](/coil/api/coil-core/coil3.fetch/-fetcher), and [Decoders](/coil/api/coil-core/coil3.decode/-decoder).

Custom components must be added to the `ImageLoader` when constructing it through its [ComponentRegistry](/coil/api/coil-core/coil3/-component-registry):

```kotlin
val imageLoader = ImageLoader.Builder(context)
    .components {
        add(CustomCacheInterceptor())
        add(ItemMapper())
        add(HttpUrlKeyer())
        add(CronetFetcher.Factory())
        add(GifDecoder.Factory())
    }
    .build()
```

## Interceptors

Interceptors allow you to observe, transform, short circuit, or retry requests to an `ImageLoader`'s image engine. For example, you can add a custom cache layer like so:

```kotlin
class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache<String, Image>,
) : Interceptor {

    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
        val value = cache.get(chain.request.data.toString())
        if (value != null) {
            return SuccessResult(
                image = value.bitmap.asImage(),
                request = chain.request,
                dataSource = DataSource.MEMORY_CACHE,
            )
        }
        return chain.proceed()
    }
}
```

Interceptors are an advanced feature that let you wrap an `ImageLoader`'s image pipeline with custom logic. Their design is heavily based on [OkHttp's `Interceptor` interface](https://lysine.dev/okhttp/interceptors/#interceptors).

See [Interceptor](/coil/api/coil-core/coil3.intercept/-interceptor) for more information.

## Mappers

Mappers allow you to add support for custom data types. For instance, say we get this model from our server:

```kotlin
data class Item(
    val id: Int,
    val imageUrl: String,
    val price: Int,
    val weight: Double
)
```

We could write a custom mapper to map it to its URL, which will be handled later in the pipeline:

```kotlin
class ItemMapper : Mapper<Item, String> {
    override fun map(data: Item, options: Options) = data.imageUrl
}
```

After registering it when building our `ImageLoader` (see above), we can safely load an `Item`:

```kotlin
val request = ImageRequest.Builder(context)
    .data(item)
    .target(imageView)
    .build()
imageLoader.enqueue(request)
```

See [Mapper](/coil/api/coil-core/coil3.map/-mapper) for more information.

## Keyers

Keyers convert data into a portion of a cache key. This value is used as `MemoryCache.Key.key` when/if this request's output is written to the `MemoryCache`.

See [Keyers](/coil/api/coil-core/coil3.key/-keyer) for more information.

## Fetchers

Fetchers translate data (e.g. URL, URI, File, etc.) into either an `ImageSource` or an `Image`. They typically convert the input data into a format that can then be consumed by a `Decoder`. Use this interface to add support for custom fetching mechanisms (e.g. Cronet, custom URI schemes, etc.)

See [Fetcher](/coil/api/coil-core/coil3.fetch/-fetcher) for more information.

!!! Note
    If you add a `Fetcher` that uses a custom data type, you need to also need to provide a custom `Keyer` to ensure results from requests that use it are memory cacheable. For example, `Fetcher.Factory<MyDataType>` will need to add a `Keyer<MyDataType`.

## Decoders

Decoders read an `ImageSource` and return an `Image`. Use this interface to add support for custom file formats (e.g. GIF, SVG, TIFF, etc.).

See [Decoder](/coil/api/coil-core/coil3.decode/-decoder) for more information.

## Custom ImageLoader and ImageRequest properties

Coil supports attaching custom data to `ImageRequest`s and `ImageLoader`s through their `Extras`. `Extras` are a map of extra properties that are referenced via an `Extras.Key`.

For example, say we want to support a custom timeout for each `ImageRequest`. We could add custom extension functions for it like so:

```kotlin
fun ImageRequest.Builder.timeout(timeout: Duration) = apply {
    extras[timeoutKey] = timeout
}

fun ImageLoader.Builder.timeout(timeout: Duration) = apply {
    extras[timeoutKey] = timeout
}

val ImageRequest.timeout: Duration
    get() = getExtra(timeoutKey)

val Options.timeout: Duration
    get() = getExtra(timeoutKey)

// NOTE: Extras.Key instances should be declared statically as they're compared with instance equality.
private val timeoutKey = Extras.Key(default = Duration.INFINITE)
```

Then we can read the property inside a custom `Interceptor` that we'll register in our `ImageLoader`:

```kotlin
class TimeoutInterceptor : Interceptor {
    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
        val timeout = chain.request.timeout
        if (timeout.isFinite()) {
            return withTimeout(timeout) {
                chain.proceed()
            }
        } else {
            return chain.proceed()
        }
    }
}
```

Finally, we can set the property when creating our `ImageRequest`:

```kotlin
AsyncImage(
    model = ImageRequest.Builder(LocalPlatformContext.current)
        .data("https://example.com/image.jpg")
        .timeout(10.seconds)
        .build(),
    contentDescription = null,
)
```

Additionally:

- We can set a default timeout value via the `ImageLoader.Builder.timeout` extension function we defined.
- We can read the timeout inside `Mapper`s, `Fetcher`s, and `Decoder`s via the `Options.timeout` extension function we defined.

[Coil uses this pattern itself](https://github.com/coil-kt/coil/blob/main/coil-gif/src/main/java/coil3/gif/imageRequests.kt) to support custom request properties for GIFs in `coil-gif` as well as other extension libraries.

## Chaining components

A useful property of Coil's image loader components is that they can be chained internally. For example, say you need to perform a network request to get the image URL that will be loaded.

First, let's create a custom data type that only our fetcher will handle:

```kotlin
data class PartialUrl(
    val baseUrl: String,
)
```

Then let's create our custom `Fetcher` that will get the image URL and delegate to the internal network fetcher:

```kotlin
class PartialUrlFetcher(
    private val callFactory: Call.Factory,
    private val partialUrl: PartialUrl,
    private val options: Options,
    private val imageLoader: ImageLoader,
) : Fetcher {

    override suspend fun fetch(): FetchResult? {
        val request = Request.Builder()
            .url(partialUrl.baseUrl)
            .build()
        val response = callFactory.newCall(request).await()

        // Read the image URL.
        val imageUrl: String = readImageUrl(response.body)

        // This will delegate to the internal network fetcher.
        val data = imageLoader.components.map(imageUrl, options)
        val output = imageLoader.components.newFetcher(data, options, imageLoader)
        val (fetcher) = checkNotNull(output) { "no supported fetcher" }
        return fetcher.fetch()
    }

    class Factory(
        private val callFactory: Call.Factory = OkHttpClient(),
    ) : Fetcher.Factory<PartialUrl> {
        override fun create(data: PartialUrl, options: Options, imageLoader: ImageLoader): Fetcher {
            return PartialUrlFetcher(callFactory, data, options, imageLoader)
        }
    }
}
```

Finally all we have to do is register the `Fetcher` in our `ComponentRegistry` and pass a `PartialUrl` as our `model`/`data`:

```kotlin
AsyncImage(
    model = PartialUrl("https://example.com/image.jpg"),
    contentDescription = null,
)
```

This pattern can similarly be applied to `Mapper`s, `Keyer`s, and `Decoder`s.

---

### Image Requests (`docs/image_requests.md`)

# Image Requests

`ImageRequest`s are [value objects](https://publicobject.com/2019/06/10/value-objects-service-objects-and-glue/) that provide all the necessary information for an [ImageLoader](image_loaders.md) to load an image. `ImageRequest`s can be created using a builder:

```kotlin
val request = ImageRequest.Builder(context)
    .data("https://example.com/image.jpg")
    .crossfade(true)
    .target(imageView)
    .build()
```

Once you've created a request pass it to an `ImageLoader` to enqueue/execute it:

```kotlin
imageLoader.enqueue(request)
```

See the [API documentation](/coil/api/coil-core/coil3.request/-image-request/) for more information.

!!! Notes
    In Coil 3.x `ImageRequest`'s platform-specific functions (e.g. `ImageRequest.Builder.target(ImageView)`) are implemented as extension functions and need to be imported separately.

---

### Java Compatibility (`docs/java_compatibility.md`)

# Java Compatibility

Coil's API is designed to be Kotlin-first. It leverages Kotlin language features such as inlined lambdas, receiver params, default arguments, and extension functions, which are not available in Java.

Importantly, suspend functions cannot be implemented in Java. This means custom [Transformations](/coil/api/coil-core/coil3.transform/-transformation), [Size Resolvers](/coil/api/coil-core/coil3.size/-size-resolver), [Fetchers](image_pipeline.md#fetchers), and [Decoders](image_pipeline.md#decoders) **must** be implemented in Kotlin.

Despite these limitations, most of Coil's API is Java compatible. You can get the singleton `ImageLoader` using:

```java
ImageLoader imageLoader = SingletonImageLoader.get(context);
```

The syntax to enqueue an `ImageRequest` is almost the same in Java and Kotlin:

```java
ImageRequest request = new ImageRequest.Builder(context)
    .data("https://example.com/image.jpg")
    .crossfade(true)
    .target(new ImageViewTarget(imageView))
    .build();
imageLoader.enqueue(request);
```

!!! Note
    `ImageView.load` cannot be used from Java. Use the `ImageRequest.Builder` API instead.

`suspend` functions cannot be easily called from Java. Thus, to get an image synchronously you'll have to use the `ImageLoader.executeBlocking` extension function which can be called from Java like so:

```java
ImageRequest request = new ImageRequest.Builder(context)
    .data("https://example.com/image.jpg")
    .size(1080, 1920)
    .build();
Image image = ImageLoaders.executeBlocking(imageLoader, request).getImage();
Drawable drawable = Image_androidKt.asDrawable(image, context. resources);
```

!!! Note
    `ImageLoaders.executeBlocking` will block the current thread instead of suspending. Do not call this from the main thread.

---

### Migrating (`docs/migrating.md`)

# Migrating from Glide/Picasso

Here are a few examples of how to migrate Glide/Picasso calls into Coil calls:

### Basic Usage

```kotlin
// Glide
Glide.with(context)
    .load(url)
    .into(imageView)

// Picasso
Picasso.get()
    .load(url)
    .into(imageView)

// Coil
imageView.load(url)
```

### Custom Requests

```kotlin
imageView.scaleType = ImageView.ScaleType.FIT_CENTER

// Glide
Glide.with(context)
    .load(url)
    .placeholder(placeholder)
    .fitCenter()
    .into(imageView)

// Picasso
Picasso.get()
    .load(url)
    .placeholder(placeholder)
    .fit()
    .into(imageView)

// Coil (automatically detects the scale type)
imageView.load(url) {
    placeholder(placeholder)
}
```

### Non-View Targets

```kotlin
// Glide (has optional callbacks for start and error)
Glide.with(context)
    .load(url)
    .into(object : CustomTarget<Drawable>() {
        override fun onResourceReady(resource: Drawable, transition: Transition<Drawable>) {
            // Handle the successful result.
        }

        override fun onLoadCleared(placeholder: Drawable) {
            // Remove the drawable provided in onResourceReady from any Views and ensure no references to it remain.
        }
    })

// Picasso
Picasso.get()
    .load(url)
    .into(object : BitmapTarget {
        override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
            // Handle the successful result.
        }

        override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) {
            // Handle the error drawable.
        }

        override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
            // Handle the placeholder drawable.
        }
    })

// Coil
val request = ImageRequest.Builder(context)
    .data(url)
    .target(
        onStart = { placeholder ->
            // Handle the placeholder image.
        },
        onSuccess = { result ->
            // Handle the successful result.
        },
        onError = { error ->
            // Handle the error image.
        }
    )
    .build()
context.imageLoader.enqueue(request)
```

### Background Thread

```kotlin
// Glide (blocks the current thread; must not be called from the main thread)
val drawable = Glide.with(context)
    .load(url)
    .submit(width, height)
    .get()

// Picasso (blocks the current thread; must not be called from the main thread)
val drawable = Picasso.get()
    .load(url)
    .resize(width, height)
    .get()

// Coil (suspends, non-blocking, and thread safe)
val request = ImageRequest.Builder(context)
    .data(url)
    .size(width, height)
    .build()
val drawable = context.imageLoader.execute(request).image.asDrawable(resources)
```

---

### Recipes (`docs/recipes.md`)

# Recipes

This page provides guidance on how to handle some common use cases with Coil. You might have to modify this code to fit your exact requirements, but it should hopefully give you a push in the right direction!

See a common use case that isn't covered? Feel free to submit a PR with a new section.

## Palette

[Palette](https://developer.android.com/training/material/palette-colors?hl=en) allows you to extract prominent colors from an image. To create a `Palette`, you'll need access to an image's `Bitmap`. This can be done in a number of ways:

You can get access to an image's bitmap by setting a `ImageRequest.Listener` and enqueuing an `ImageRequest`:

```kotlin
imageView.load("https://example.com/image.jpg") {
    // Disable hardware bitmaps as Palette needs to read the image's pixels.
    allowHardware(false)
    listener(
        onSuccess = { _, result ->
            // Create the palette on a background thread.
            Palette.Builder(result.image.toBitmap()).generate { palette ->
                // Consume the palette.
            }
        }
    )
}
```

## Using a Memory Cache Key as a Placeholder

Using a previous request's `MemoryCache.Key` as a placeholder for a subsequent request can be useful if the two images are the same, though loaded at different sizes. For instance, if the first request loads the image at 100x100 and the second request loads the image at 500x500, we can use the first image as a synchronous placeholder for the second request.

Here's what this effect looks like in the sample app:

<p style="text-align: center;">
    <video width="360" height="640" autoplay loop muted playsinline>
        <source src="../images/crossfade.mp4" type="video/mp4">
    </video>
</p>

*Images in the list have intentionally been loaded with very low detail and the crossfade is slowed down to highlight the visual effect.*

To achieve this effect, use the `MemoryCache.Key` of the first request as the `ImageRequest.placeholderMemoryCacheKey` of the second request. Here's an example:

```kotlin
// First request
listImageView.load("https://example.com/image.jpg")

// Second request (once the first request finishes)
detailImageView.load("https://example.com/image.jpg") {
    placeholderMemoryCacheKey(listImageView.result.memoryCacheKey)
}
```

## Compose AnimatedContent

Use `rememberAsyncImagePainter` and `AnimatedContent` to animate between a placeholder and the loaded image:

```kotlin
val sizeResolver = rememberConstraintsSizeResolver()
val painter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalPlatformContext.current)
        .data("https://example.com/image.jpg")
        .size(sizeResolver)
        .build(),
)
val state by painter.state.collectAsState()

AnimatedContent(
    modifier = Modifier.then(sizeResolver),
    targetState = state,
    contentKey = { it::class },
    transitionSpec = { fadeIn() togetherWith fadeOut() },
) { targetState ->
    when (targetState) {
        is AsyncImagePainter.State.Empty,
        is AsyncImagePainter.State.Loading -> PlaceholderContent()
        is AsyncImagePainter.State.Success -> {
            // Keep the old image visible while it animates out after the model changes.
            Image(
                painter = targetState.painter,
                contentDescription = stringResource(R.string.description),
            )
        }
        is AsyncImagePainter.State.Error -> ErrorContent()
    }
}
```

Pass `targetState.painter` to `Image` so an old image remains visible while it animates out after the model changes.

The final image size is unknown until loading finishes. Use fixed constraints or a known aspect ratio to reserve space.

`rememberAsyncImagePainter` starts in `State.Empty`, even when the image is in the memory cache. Use `state.result.dataSource` to skip an animation for memory-cached images if needed.

NOTE: `AnimatedContent` is much more expensive than a painter crossfade and keeps the old image in memory until the animation ends. Prefer `ImageRequest.Builder.crossfade` in lazy lists or for a simple fade.

## Shared Element Transitions

[Shared element transitions](https://developer.android.com/training/transitions/start-activity) allow you to animate between `Activities` and `Fragments`. Here are some recommendations on how to get them to work with Coil:

- **Shared element transitions are incompatible with hardware bitmaps.** You should set `allowHardware(false)` to disable hardware bitmaps for both the `ImageView` you are animating from and the view you are animating to. If you don't, the transition will throw an `java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps` exception.

- Use the `MemoryCache.Key` of the start image as the [`placeholderMemoryCacheKey`](/coil/api/coil-core/coil3.request/-image-request/-builder/placeholder-memory-cache-key) for the end image. This ensures that the start image is used as the placeholder for the end image, which results in a smooth transition with no white flashes if the image is in the memory cache.

- Use [`ChangeImageTransform`](https://developer.android.com/reference/android/transition/ChangeImageTransform) and [`ChangeBounds`](https://developer.android.com/reference/android/transition/ChangeBounds) together for optimal results.

Using Compose? [Check out this article for how to perform shared element transitions with `AsyncImage`](https://www.tunjid.com/articles/animating-contentscale-during-image-shared-element-transitions-65fba03537c67f8df0161c31).

## Remote Views

Coil does not provide a `Target` for [`RemoteViews`](https://developer.android.com/reference/android/widget/RemoteViews) out of the box, however you can create one like so:

```kotlin
class RemoteViewsTarget(
    private val context: Context,
    private val componentName: ComponentName,
    private val remoteViews: RemoteViews,
    @IdRes private val imageViewResId: Int
) : Target {

    override fun onStart(placeholder: Image?) = setImage(placeholder)

    override fun onError(error: Image?) = setImage(error)

    override fun onSuccess(result: Image) = setImage(result)

    private fun setImage(image: Image?) {
        remoteViews.setImageViewBitmap(imageViewResId, image?.toBitmap())
        AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteViews)
    }
}
```

Then `enqueue`/`execute` the request like normal:

```kotlin
val request = ImageRequest.Builder(context)
    .data("https://example.com/image.jpg")
    .target(RemoteViewsTarget(context, componentName, remoteViews, imageViewResId))
    .build()
imageLoader.enqueue(request)
```

## Transforming Painters

Both `AsyncImage` and `AsyncImagePainter` have `placeholder`/`error`/`fallback` arguments that accept `Painter`s. Painters are less flexible than using composables, but are faster as Coil doesn't need to use subcomposition. That said, it may be necessary to inset, stretch, tint, or transform your painter to get the desired UI. To accomplish this, [copy this Gist into your project](https://gist.github.com/colinrtwhite/c2966e0b8584b4cdf0a5b05786b20ae1) and wrap the painter like so:

```kotlin
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null,
    placeholder = forwardingPainter(
        painter = painterResource(R.drawable.placeholder),
        colorFilter = ColorFilter(Color.Red),
        alpha = 0.5f,
    ),
)
```

The `onDraw` can be overwritten using a trailing lambda:

```kotlin
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null,
    placeholder = forwardingPainter(painterResource(R.drawable.placeholder)) { info ->
        inset(50f, 50f) {
            with(info.painter) {
                draw(size, info.alpha, info.colorFilter)
            }
        }
    },
)
```

## Transforming Requests

You may need to transform HTTP requests used to fetch images. In this example, we'll use an [Interceptor](https://coil-kt.github.io/coil/api/coil-core/coil3.intercept/-interceptor) to append `width` & `height` query parameters to the request URL.

```kotlin
class UrlSizeInterceptor : Interceptor {

    override suspend fun intercept(chain: Chain): ImageResult {
        val request = chain.request
        val uri = request.uri

        if (uri == null || uri.scheme !in setOf("https", "http")) {
            // Ignore non-HTTP requests.
            return chain.proceed()
        }

        val (width, height) = chain.size
        return if (width is Pixels && height is Pixels) {
            val transformUri = uri.newBuilder()
                .query("width=${width.px}&height=${height.px}")
                .build()

            val transformedRequest = request.newBuilder()
                .data(transformUri)
                .build()
            return chain.withRequest(transformedRequest).proceed()
        } else {
            // Width & height aren't available, i.e. because of infinite constraints.
            chain.proceed()
        }
    }

    private val ImageRequest.uri: Uri?
        get() = when (val data = data) {
            is Uri -> data
            is coil3.Uri -> data.toAndroidUri()
            is String -> data.toUri()
            else -> null
        }
}
```

Don't forget to register add interceptor to your `ImageLoader`!

```kotlin
ImageLoader.Builder(context)
    .components {
        add(UrlSizeInterceptor())
    }
    .build()
```

---

### Upgrading To Coil2 (`docs/upgrading_to_coil2.md`)

# Upgrading to Coil 2.x

This is a short guide to highlight the main changes when upgrading from Coil 1.x to 2.x and how to handle them. This upgrade guide doesn't cover every binary or source incompatible change, but it does cover the most important changes.

## Minimum API 21

Coil 2.x requires minimum API 21. This is also the minimum API required for Compose and OkHttp 4.x.

## ImageRequest default scale

Coil 2.x changes `ImageRequest`'s default scale from `Scale.FILL` to `Scale.FIT`. This was done to be consistent with `ImageView`'s default `ScaleType` and `Image`'s default `ContentScale`. Scale is still autodetected if you set an `ImageView` as your `ImageRequest.target`.

## Size refactor

`Size`'s `width` and `height` are now two `Dimension`s instead of `Int` pixel values. `Dimension` is either a pixel value or `Dimension.Undefined`, which represents an undefined/unbounded constraint. For example, if the size is `Size(400, Dimension.Undefined)` that means the image should be scaled to have 400 pixels for its width irrespective of its height. You can use the `pxOrElse` extension to get the pixel value (if present), else use a fallback:

```kotlin
val width = size.width.pxOrElse { -1 }
if (width > 0) {
    // Use the pixel value.
}
```

This change was made to improve support for cases where a target has one unbounded dimension (e.g. if one dimension is `ViewGroup.LayoutParams.WRAP_CONTENT` for a `View` or `Constraints.Infinity` in Compose).

## Compose

Coil 2.x significantly reworks the Compose integration to add features, improve stability, and improve performance.

In Coil 1.x you would use `rememberImagePainter` to load an image:

```kotlin
val painter = rememberImagePainter("https://example.com/image.jpg") {
    crossfade(true)
}

Image(
    painter = painter,
    contentDescription = null,
    contentScale = ContentScale.Crop
)
```

In Coil 2.x `rememberImagePainter` has been changed to `rememberAsyncImagePainter` with the following changes:

- The trailing lambda argument to configure the `ImageRequest` has been removed.
- In Coil 2.x, `rememberAsyncImagePainter` defaults to using `ContentScale.Fit` to be consistent with `Image` whereas in Coil 1.x it would default to `ContentScale.Crop`. As such, if you set a custom `ContentScale` on `Image`, you now also need to pass it to `rememberAsyncImagePainter`.

```kotlin
val painter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/image.jpg")
        .crossfade(true)
        .build(),
    contentScale = ContentScale.Crop
)

Image(
    painter = painter,
    contentDescription = null,
    contentScale = ContentScale.Crop
)
```

Additionally, Coil now has `AsyncImage` and `SubcomposeAsyncImage` composable functions, which add new features and work-around some design limitations of `rememberAsyncImagePainter`. Check out the full Compose docs [here](compose.md).

## Disk Cache

Coil 2.x has its own public disk cache class that can be accessed using `imageLoader.diskCache`. Coil 1.x relied on OkHttp's disk cache, however it's no longer needed.

To configure the disk cache in 1.x you would use `CoilUtils.createDefaultCache`:

```kotlin
ImageLoader.Builder(context)
    .okHttpClient {
        OkHttpClient.Builder().cache(CoilUtils.createDefaultCache(context)).build()
    }
    .build()
```

In Coil 2.x you should not set a `Cache` object on your `OkHttpClient` when used with an `ImageLoader`. Instead configure the disk cache object like so:

```kotlin
ImageLoader.Builder(context)
    .diskCache {
        DiskCache.Builder()
            .directory(context.cacheDir.resolve("image_cache"))
            .build()
    }
    .build()
```

This change was made to add functionality and improve performance:

- Support thread interruption while decoding images.
  - Thread interruption allows fast cancellation of decode operations. This is particularly important for quickly scrolling through a list.
  - By using a custom disk cache Coil is able to ensure a network source is fully read to disk before decoding. This is necessary as writing the data to disk cannot be interrupted - only the decode step can be interrupted. OkHttp's `Cache` shouldn't be used with Coil 2.0 as it's not possible to guarantee that all data is written to disk before decoding.
- Avoid buffering/creating temporary files for decode APIs that don't support `InputStream`s or require direct access to a `File` (e.g. `ImageDecoder`, `MediaMetadataRetriever`).
- Add a public read/write `DiskCache` API.

In Coil 2.x `Cache-Control` and other cache headers are still supported - except `Vary` headers, as the cache only checks that the URLs match. Additionally, only responses with a response code in the range [200..300) are cached.

When upgrading from Coil 1.x to 2.x, any existing disk cache will be cleared as the internal format has changed.

## Image pipeline refactor

Coil 2.x refactors the image pipeline classes to be more flexible. Here's a high-level list of the changes:

- Introduce a new class, `Keyer`, that computes the memory cache key for a request. It replaces `Fetcher.key`.
- `Mapper`, `Keyer`, `Fetcher`, and `Decoder` can return `null` to delegate to the next element in the list of components.
- Add `Options` to `Mapper.map`'s signature.
- Introduce `Fetcher.Factory` and `Decoder.Factory`. Use the factories to determine if a specific `Fetcher`/`Decoder` is applicable. Return `null` if that `Fetcher`/`Decoder` is not applicable.

## Remove bitmap pooling

Coil 2.x removes bitmap pooling and its associated classes (`BitmapPool`, `PoolableViewTarget`). See [here](https://github.com/coil-kt/coil/discussions/1186#discussioncomment-2305528) for why it was removed.

---

### Upgrading To Coil3 (`docs/upgrading_to_coil3.md`)

# Upgrading to Coil 3.x

Coil 3 is the next major version of Coil that has a number of major improvements:

- Full support for [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/) including all major targets (Android, iOS, JVM, JS, and [WASM](https://coil-kt.github.io/coil/sample/)).
- Support for multiple networking libraries (Ktor and OkHttp). Alternatively, Coil can be used without a network dependency if you only need to load local/static files.
- Improved Compose `@Preview` rendering and support for custom preview behavior via `LocalAsyncImagePreviewHandler`.
- Important fixes for bugs that required breaking existing behaviour (outlined below).

This document provides a high-level overview of the main changes from Coil 2 to Coil 3 and highlights any breaking or important changes. It does not cover every binary incompatible change or small behaviour changes.

Using Coil 3 in a Compose Multiplatform project? Check out the [`samples`](https://github.com/coil-kt/coil/tree/3.x/samples/compose) repository for examples.

## Maven Coordinates and Package Name

Coil's Maven coordinates were updated from `io.coil-kt` to `io.coil-kt.coil3` and its package name was updated from `coil` to `coil3`. This allows Coil 3 to run side by side with Coil 2 without binary compatibility issues. For example, `io.coil-kt:coil:2.7.0` is now `io.coil-kt.coil3:coil:3.0.0`.

The `coil-base` and `coil-compose-base` artifacts were renamed to `coil-core` and `coil-compose-core` respectively to align with the naming conventions used by Coroutines, Ktor, and AndroidX.

## Network Images

**`coil-core` no longer supports loading images from the network by default.** [You must add a dependency on one of Coil's network artifacts. See here for more info.](network.md). This was changed so consumers could use different networking libraries or avoid a network dependency if their app doesn't need it.

Additionally, cache control headers are no longer respected by default. See [here](network.md) for more info.

## Multiplatform

Coil 3 is now a Kotlin Multiplatform library that supports Android, JVM, iOS, macOS, Javascript, and WASM.

On Android, Coil uses the standard graphics classes to render images. On non-Android platforms, Coil uses [Skiko](https://github.com/JetBrains/skiko) to render images. Skiko is a set of Kotlin bindings that wrap the [Skia](https://github.com/google/skia) graphics engine developed by Google.

As part of decoupling from the Android SDK, a number of API changes were made. Notably:

- `Drawable` was replaced with a custom `Image` interface. Use `Drawable.asImage()` and `Image.asDrawable(resources)` to convert between the classes on Android. On non-Android platforms use `Bitmap.asImage()` and `Image.toBitmap()`.
- Usages of Android's `android.net.Uri` class were replaced a multiplatform `coil3.Uri` class. Any call sites that pass `android.net.Uri` as `ImageRequest.data` are unaffected. Custom `Fetcher`s that rely on receiving an `android.net.Uri` will need to be updated to use `coil3.Uri`.
- Usages of `Context` were replaced with `PlatformContext`. `PlatformContext` is a type alias for `Context` on Android and can be accessed using `PlatformContext.INSTANCE` on non-Android platforms. Use `LocalPlatformContext.current` to get a reference in Compose Multiplatform.
- The `Coil` class was renamed to `SingletonImageLoader`.
- If you're implementing `ImageLoaderFactory` in your custom Android `Application` class, you'll need to switch to implementing `SingletonImageLoader.Factory` as a replacement for `ImageLoaderFactory`. Once you implement `SingletonImageLoader.Factory`, you'll be able to override `newImageLoader()` if you need or want to override it.

The `coil-svg` artifact is supported in multiplatform, but the `coil-gif` and `coil-video` artifacts continue to be Android-only (for now) as they rely on specific Android decoders and libraries.

## Compose

The `coil-compose` artifact's APIs are mostly unchanged. You can continue using `AsyncImage`, `SubcomposeAsyncImage`, and `rememberAsyncImagePainter` the same way as with Coil 2. Additionally, this methods have been updated to be [restartable and skippable](https://developer.android.com/jetpack/compose/performance/stability) which should improve their performance.

- `AsyncImagePainter.state` is now a `StateFlow`. It should be observed using `val state = painter.state.collectAsState()`.
- `AsyncImagePainter`'s default `SizeResolver` no longer waits for the first `onDraw` call to get the size of the canvas. Instead, `AsyncImagePainter` defaults to `Size.ORIGINAL`.
- The Compose `modelEqualityDelegate` delegate is now set via a composition local, `LocalAsyncImageModelEqualityDelegate`, instead of as a parameter to `AsyncImage`/`SubcomposeAsyncImage`/`rememberAsyncImagePainter`.

## General

Other important behavior changes include:

- First party `Fetcher`s and `Decoder`s (e.g. `NetworkFetcher.Factory`, `SvgDecoder`, etc.) are now automatically added to each new `ImageLoader` through a service loader. This behaviour can be disabled with `ImageLoader.Builder.serviceLoaderEnabled(false)`.
- Remove support for `android.resource://example.package.name/drawable/image` URIs as it prevents resource shrinking optimizations. It's recommended to pass `R.drawable.image` values directly. Passing the resource ID instead of the resource name will still work: `android.resource://example.package.name/12345678`. If you still needs its functionality you can [manually include `ResourceUriMapper` in your component registry](https://github.com/coil-kt/coil/blob/da7d872e340430014dbc5136e35eb62f9b17662e/coil-core/src/androidInstrumentedTest/kotlin/coil3/map/ResourceUriMapper.kt).
- A file's last write timestamp is no longer added to its cache key by default. This is to avoid reading the disk on the main thread (even for a very short amount of time). This can be re-enabled with `ImageRequest.Builder.addLastModifiedToFileCacheKey(true)` or `ImageLoader.Builder.addLastModifiedToFileCacheKey(true)`.
- Output image dimensions are now enforced to be less than 4096x4096 to guard against accidental OOMs. This can be configured with `ImageLoader/ImageRequest.Builder.maxBitmapSize`. To disable this behavior set `maxBitmapSize` to `Size.ORIGINAL`.
- Coil 2's `Parameters` API was replaced by `Extras`. `Extras` don't require a string key and instead rely on identity equality. `Extras` don't support modifying the memory cache key. Instead, use `ImageRequest.memoryCacheKeyExtra` if your extra affects the memory cache key.
- Many `ImageRequest.Builder` functions have moved to be extension functions to more easily support multiplatform.

---

### Works With Coil (`docs/works_with_coil.md`)

# Works with Coil

A collection of third party libraries that work nicely with Coil.

- [Telephoto](https://github.com/saket/telephoto): Building blocks for designing media experiences in Compose UI.
- [Landscapist](https://github.com/skydoves/landscapist): A pluggable, highly optimized Jetpack Compose and Kotlin Multiplatform image loading library that fetches and displays network images with Glide, Coil, and Fresco.
- [coil-resvg](https://github.com/hash-sequence/coil-resvg): Render SVGs with [resvg](https://github.com/linebender/resvg) in Coil.

Have a library that works with or builds on top of Coil? Submit a PR to include it here!

---

--- METRICS ---
- Files Extracted: 13
- Estimated Token Budget: ~14064 tokens
- Canonical Reference: https://codewiki.google/github.com/coil-kt/coil
