Coil is a powerful, fast, lightweight image-loading library that many Android developers have used for years. After the introduction of compose multiplatform, several open source image loading libraries have been introduced by the community such as Kamel and Compose Image Loader.
There was still an option of using coil using expect and actual but only on Android.
However, thanks to the Coil team, announced they will be working on supporting compose multiplatform. The good news is that Coil3 supports Android, IOS, Desktop and Wasm.
Let us learn step by step how to start using coil3 for the CMP project.
Setup:
Let’s start with dependencies
coil3 = "3.0.0-alpha06"
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" }
coil-compose-core = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil3" }
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil3" }
coil-mp = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" }
Coil 3 needs Ktor wasm to support image loading on the wasm platform.
ktor = "3.0.0-wasm2"
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
You also need to add this repository to the settings.gradle plugin management block.
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
maven( "https://androidx.dev/storage/compose-compiler/repository")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental")
maven( "https://androidx.dev/storage/compose-compiler/repository")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
}
}
Now add these to your build.gradle in the common module
implementation(libs.ktor.core)
implementation(libs.coil.compose.core)
implementation(libs.coil.compose)
implementation(libs.coil.mp)
implementation(libs.coil.network.ktor)
previously in Android, we used to do this
class MyApplication : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this)
.crossfade(true)
.build()
}
}
in Compose multi-platform we have to build an image loader as well
let’s create the image loader
fun getAsyncImageLoader(context: PlatformContext)=
ImageLoader.Builder(context).crossfade(true).logger(DebugLogger()).build()
Notice that similar to what we used to do in Android the builder still needs a context, but the context does not exist in KMP, We can get the platform context using a composition local
LocalPlatformContext.current
but we don’t need it yet, since like context we don’t have an application class in kmp so there’s a simple function in coil which you need to call in the start of your top-level composable.
internal fun App() = AppTheme {
setSingletonImageLoaderFactory { context ->
getAsyncImageLoader(context)
}
//your content here
}
The official doc describes it like this
and that’s all the setup. Let us start loading images now.
Just like Android, you can use AsyncImage and SubComposeAsyncImage.
Async Image:
coil3.compose.AsyncImage(
modifier = modifier,
model =url,
contentDescription = contentDescription,
contentScale = contentScale,
onError = {
//update state
},
onLoading = {
//update state
},
onSuccess = {
//update state
},
)
SubComposeAsyncImage:
SubcomposeAsyncImage(
modifier = modifier,
model = url,
loading = {
Text("Loading")
},
contentScale = contentScale,
contentDescription = contentDescription
)
this is the same as Android let’s now move to caching and caching policies.
Memory Cache:
To use the memory cache you need to add some new parameters to the image loader that we created earlier, First enable the memory cache policy
ImageLoader.Builder(context).memoryCachePolicy(CachePolicy.ENABLED)
now inside the memory cache lambda create a memory cache object, maxSizePercent defines how much space in memory should be used in memory to cache the images.
.memoryCache {
MemoryCache.Builder().maxSizePercent(context, 0.3).strongReferencesEnabled(true).build()
}
you can also enable /disable strong and weak references.
/**
* Enables/disables strong reference tracking of values added to this memory cache.
*/
fun strongReferencesEnabled(enable: Boolean) = apply {
this.strongReferencesEnabled = enable
}
/**
* Enables/disables weak reference tracking of values added to this memory cache.
* Weak references do not contribute to the current size of the memory cache.
* This ensures that if a [Value] hasn't been garbage collected yet it will be
* returned from the memory cache.
*/
fun weakReferencesEnabled(enable: Boolean) = apply {
this.weakReferencesEnabled = enable
}
result, notice we are also passing a logger to check when the library is loading from memory and when from the cache.
Job Offers
fun getAsyncImageLoader(context: PlatformContext) =
ImageLoader.Builder(context).memoryCachePolicy(CachePolicy.ENABLED).memoryCache {
MemoryCache.Builder().maxSizePercent(context, 0.3).strongReferencesEnabled(true).build()
}.crossfade(true).logger(DebugLogger()).build()
Disk Cache:
Disk cache differs in all the different platforms, but luckily we have okio,
with coil and OKIO we can similarly use disk cache like memory cache. Before proceeding, we’ll need to make some changes to our getAsyncImageLoader method.
fun getAsyncImageLoader(context: PlatformContext) =
ImageLoader.Builder(context).diskCachePolicy(CachePolicy.ENABLED).networkCachePolicy(CachePolicy.ENABLED).diskCache {
newDiskCache()
}.crossfade(true).logger(DebugLogger()).build()
fun newDiskCache(): DiskCache {
return DiskCache.Builder().directory(FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "image_cache")
.maxSizeBytes(512L * 1024 * 1024) / 512MB
.build()
}
We need a simple disk cache object to handle disk cache, where we can set the directory and maximum size in bytes. Coil truly stands out as a potent, user-friendly, and uncomplicated library for this purpose.
Now the image is first loaded from the network and the second time its loaded from the disk.
Let’s do some troubleshooting, when running the desktop app you might get this exception
Exception in thread "main" java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'
to resolve this, we need to use swing coroutines
kotlinxCoroutinesSwing = "1.8.0"
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutinesSwing" }
add it to jvmMain
jvmMain.dependencies {
implementation(libs.ktor.java)
runtimeOnly(libs.kotlinx.coroutines.swing)
}
it should now load perfectly.
Happy Coding ❤
All the code is available in this repo
https://github.com/Kashif-E/Coil3-Compose-Multiplatform?source=post_page—–5745ea76356f——————————–
Connect with me on Linkedin:
https://www.linkedin.com/in/kashif-mehmood-km/
This is previously published on proandroiddev.com