Multiplatform image loading: Coil 3.0

Coil 3.0 is the next major iteration of Coil that focuses on adding support for Kotlin Multiplatform. By making Coil multiplatform this gives us a path to support Compose Multiplatform and run on iOS, desktop, and web. Coil 3.0 isn’t ready yet (and won’t be for a few months), but I want to briefly explore the plan to convert Coil’s codebase to multiplatform in this blog post.

Why

Coil 2.x ships a coil-compose artifact that adds support for Jetpack Compose on Android. Notably, this adds the AsyncImage composable, which lets us easily load images from any data source (memory, disk, network) asynchronously while supporting memory and disk caching out of the box:

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

It’s simple and useful, and now that Compose Multiplatform exists, we want to be able to use the same API on iOS, web, and desktop—not just Android.

How

So how are we going to get Coil’s internals and AsyncImage to work on iOS, web, and desktop?

First, we need to decouple Coil from the Android SDK. Fortunately, Coil has always kept the Android SDK at an arm’s length by exposing platform functionality as interfaces. For example, the functionality for measuring an ImageView is an implementation of a SizeResolver, which has no Android SDK references. Another example is the functionality for decoding Bitmaps is an implementation of a Decoder, which has no Android SDK references.

One major API change that we’ll have to make is to replace uses of Android’s Drawable class with a common Image class:

interface Image {
  /** The size of the image in memory in bytes. */
  val size: Long
  /** The width of the image in pixels. */
  val width: Int
  /** The height of the image in pixels. */
  val height: Int
}

Each platform has its own graphics classes and using a common Image class lets us represent a platform’s “image” class with the minimal amount of info. Importantly, Coil doesn’t need to understand how to render an image in common code. We can convert the image back into the platform type after it’s been returned in an ImageResult and immediately before it’s rendered by the UI.

Once we’ve removed all Android SDK references from Coil’s common API, how do we actually decode images and render them in the UI without using BitmapFactory or Drawable? We can use Skiko. Skiko implements bindings for the Skia graphics engine for Kotlin Multiplatform. This will let Coil decode and draw images consistently across platforms. It’s also already a dependency of Compose Multiplatform so Coil depending on it doesn’t add any extra overhead.

There’s still a lot to do before 3.0 is released so some of this might change, but I’m confident these will be the main changes to get Coil running on iOS, web, and desktop through Compose Multiplatform!

This post is part of Cash App’s Summer of Kotlin Multiplatform series.