Loading Images with Coil in Compose Multiplatform
We take a look at how to add the popular Coil image loading library to a Compose Multiplatform project.
We’ll take a look at how to add the popular Coil image loading library to a Kotlin Multiplatform project that uses the Compose Multiplatform framework to share UI across Android, iOS, Desktop, and web.
Recently, I was exploring Kotlin Multiplatform and Compose Multiplatform for a series of articles focused on that topic (released soon). One of the things I needed to do was to load remote images from the network and display them in the app. I use Coil regularly on Android but found integrating it into a Compose Multiplatform app challenging due to a lack of documentation and resources.
This article provides a simple guide on how to add and use Coil in KMP Compose apps.
Result of loading remote images in Android, iOS, Web and Desktop apps.
Declaring the dependencies
This article is focused on using the gradle version catalog to manage dependencies. If that is not the case, you can still follow along and declare dependencies directly in the
build.gradle.kts
files.
Coil Compose
Let’s start by declaring the Coil Compose dependency in the libs.versions.toml
file. For the latest version, please check the official Coil website.
1
2
3
4
5
[versions]
coil = "3.0.4"
[libraries]
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
Coil Network
The above core library only allows local images to be loaded. To load remote images from the network, we also have to declare a separate coil-network
dependency. Coil offers different network images based on OkHttp or Ktor. Since we have a KMP project, we’ll use the Ktor one.
1
2
3
4
5
6
[versions]
coil = "3.0.4"
[libraries]
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
Ktor Engine
Next, we must declare the Ktor engine dependency for each of our supported platforms: Android, iOS, and JVM.
1
2
3
4
5
6
7
[versions]
ktor = "3.0.1"
[libraries]
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-java = { group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" }
Kotlinx Coroutines Swing
Finally, since we are using Compose on JVM/Desktop, we must also add a dependency for Coroutines Swing. Coil relies on Dispatchers.Main.immediate
to resolve images from the memory cache synchronously and kotlinx-coroutines-swing
provides support for that on JVM (non-Android) platforms (source).
Most likely, you already have this dependency in your project. If you do not, here is how to declare it.
1
2
3
4
5
[versions]
kotlinx-coroutines = "1.9.0"
[libraries]
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
Implementing the dependencies
We’ve declared the dependencies in the version catalog, now let’s add them to the apps. Open the composeApp/build.gradle.kts
and add the coil compose and coil network dependencies to the commonMain
source set.
1
2
3
4
5
6
7
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.coil.compose)
implementation(libs.coil.network.ktor)
}
}
Next, we have to add the specific Ktor engine dependency for each platform. Note that desktopMain
might also be jvmMain
in your project, depending on how you name the JVM source set.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kotlin {
sourceSets {
androidMain.dependencies {
// Ktor client dependency required for Coil
implementation(libs.ktor.client.android)
}
appleMain.dependencies {
// Ktor client dependency required for iOS
implementation(libs.ktor.client.darwin)
}
// alternatively jvmMain
desktopMain.dependencies {
// Ktor client dependency required for JVM/Desktop
implementation(libs.ktor.client.java)
implementation(libs.kotlinx.coroutines.swing)
}
}
}
Adding the Internet permission on Android
Before we can load remote images on Android, we have to declare the internet permission in the AndroidManifest
. Images will fail to load without it.
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application>
...
</application>
</manifest>
Loading Remote Images in Compose
To load remote images in Compose, we can use the AsyncImage
composable that accepts a URL to the image. It executes an asynchronous request in the background to fetch the image and display it.
To see what else the
AsyncImage
supports, like placeholders and error states, check the official documentation.
1
2
3
4
5
6
7
AsyncImage(
model = "https://spacenews.com/spacex-launches-uaes-thuraya-4-mobile-connectivity-satellite/",
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
)
If we run the app on Android, iOS, Desktop, and Web, we see that images are loaded successfully.
Result of loading remote images in Android, iOS, Web and Desktop apps.
Loading local images
Coil also supports loading local drawables. We’ve added a .jpg
file to the commonMain/composeResources/drawable
folder and want to load it in the app.
Location of a local image in the project structure.
To do that, we can pass Res.getUri(path_to_drawable)
to the model
argument of the AsyncImage
composable. We also need to add the @OptIn(ExperimentalResourceApi::class
annotation since the support for accessing resources via Uri is still experimental.
Res.drawable.image
and other compile-safe references are not supported by Coil until Compose Multiplatform exposes APIs to support it (ticket).
1
2
3
4
5
@OptIn(ExperimentalResourceApi::class)
AsyncImage(
model = Res.getUri("drawable/pexels-pixabay-2166.jpg"),
contentDescription = stringResource(Res.string.locally_loaded_image_title),
)
Here is the result: the local image is successfully loaded on all platforms.
Result of loading local images in Android, iOS, Web, and Desktop apps.
Conclusion
We’ve looked at how to add the Coil image loading library to a Kotlin Multiplatform project that uses Compose to share UIs across Android, iOS, Desktop, and Web.
As we saw, adding just Coil is not enough. We also must add dependencies for Ktor to load remote images on all platforms.
You can find the source code and the sample project on GitHub.
I hope you found this article useful. Let me know your thoughts and experiences with Coil and KMP in the comments.