Skip to main content

Migrate image loading

The generalized image pipeline replaces the old remote-only protocol with a portable source and request contract. This is a source- and binary-breaking change for applications that implemented the old loader or stored the old request type.

API mapping​

Previous APICurrent APIMigration action
RemoteImageLoaderUiImageLoaderImplement load(UiImageTarget, UiImageRequest) and return a disposable handle.
RemoteImageRequestUiImageRequest plus node fallbackMap source, placeholder, error, content scale, and UiImageRequestOptions; keep no-source fallback on Image, Icon, or IconButton.
RemoteImageTargetUiImageTargetAccept the portable target and validate the platform object in the adapter.
PlatformRemoteImageTargetPlatformUiImageTargetUse the general platform target wrapper.
ProvideRemoteImageLoaderProvideImageLoaderInstall the loader around the smallest image subtree.
CoilRemoteImageLoaderCoilImageLoaderAdapterReplace the adapter and keep the injected Coil ImageLoader caller-owned.
ImageSource.Remote(url)ImageSource.Url(url)Use Url for a URL; use Uri, File, Resource, or keyed Model for other sources.

Before and after​

Old code conceptually looked like this:

ProvideRemoteImageLoader(CoilRemoteImageLoader(imageLoader)) {
Image(source = ImageSource.Remote(url))
}

The generalized form is:

ProvideImageLoader(CoilImageLoaderAdapter(imageLoader)) {
Image(
source = ImageSource.Url(url),
requestOptions = UiImageRequestOptions(
decodeSize = UiImageDecodeSize.Target,
),
)
}

CoilImageLoaderAdapter intentionally has no Context constructor. If old code used CoilRemoteImageLoader(context), create or obtain one application-scoped Coil ImageLoader, pass it to the adapter, and shut it down only when that application owner ends.

For a non-URL source, select the matching type rather than encoding it as a URL:

Image(source = ImageSource.Model(value = model, stableKey = modelId))

ImageSource.Url now validates an absolute HTTP(S) URL. Use ImageSource.Uri for another absolute scheme. Explicit decode-size bounds use UiImageDecodeSize.Fixed(width, height) with UiDp; the renderer carries its captured density into UiImageRequest, and adapters convert those bounds to platform pixels. Fallback is intentionally absent from UiImageRequest, because a null source never starts a request.

Adapter obligations​

An adapter must map all source types it claims to support and must return a handle for the exact operation it starts. Make disposal idempotent. Do not close an injected decoder, retain a mounted View after disposal, or compare arbitrary model payloads as framework identity. Consume only extension types the adapter owns and ignore other extension types.

If the application has no decoder for a source, keep the source nullable or provide a resource fallback. Resource-only images continue to work without an adapter.

Rollout order​

  1. Update the UI contract and widget imports together.
  2. Replace the provider and adapter names.
  3. Convert ImageSource.Remote call sites to the most specific current source type.
  4. Add request options where the old adapter relied on implicit size, cache, or transition behavior.
  5. Run renderer lifecycle tests and a recycled-row/manual verification path.
  6. Remove old protocol declarations only after repository-wide production references are gone.

The image loading guide describes the ownership and disposal rules in more detail. The Image Coil manual documents the published adapter's compatibility boundary.

Unreleased resource-scope upgrade​

This checkout adds defaulted resourceCacheScope fields to UiEnvironmentValues and UiImageRequest. Recompile all consumers: Kotlin data-class constructor and copy binary signatures change even though ordinary source calls still compile.

Standard Android hosts install the scope automatically. A custom host should preferably install AndroidResourceEnvironment. Otherwise generate one process-unique scope per mount (for example a UUID), retain it through refreshes, and copy it with the local revision into resource requests. Advance the revision after every resource/theme mutation. Never persist the scope, share it across independent environments, or treat the local revision alone as cache identity.

Leaving the scope null is supported and disables resource memory caching in the built-in adapters. Primary local resource disk caching is disabled even under the default policy: the framework cannot prove a persistent fingerprint for arbitrary themed resources. Remote primary images keep their loader's normal caching. Existing files and URLs need no new keys. Recheck resource cache hit rates and theme changes when upgrading; the correctness fix does not claim equal cache performance.