Kamel
Kotlin asynchronous media loading and caching library for Compose.
Install / Use
/learn @Kamel-Media/KamelREADME
Kamel
Kamel is an asynchronous media loading library for Compose Multiplatform. It provides a simple, customizable and efficient way to load, cache, decode and display images in your application. By default, it uses Ktor client for loading resources.
Table of contents
- Setup
- Usage
- Contributions
- License
Setup
Kamel is published on Maven Central:
repositories {
mavenCentral()
// ...
}
Default Setup
Add the dependency to the common source-set or to any platform source-set:
kotlin {
sourceSets {
commonMain {
dependencies {
implementation("media.kamel:kamel-image-default:1.0.9")
// no need to specify ktor engines, one is included for each target
// ...
}
}
}
}
Granular Setup
For a more granular setup, you can choose which modules to include in your project to limit transitive dependencies:
kotlin {
sourceSets {
commonMain {
dependencies {
// core module (required)
implementation("media.kamel:kamel-image:1.0.9")
// Note: When using `kamel-image` a ktor engine is not included.
// To fetch remote images you also must ensure you add your own
// ktor engine for each target.
// optional modules (choose what you need and add them to your kamel config)
implementation("media.kamel:kamel-decoder-image-bitmap:1.0.9")
implementation("media.kamel:kamel-decoder-image-bitmap-resizing:1.0.9") // android only right now
implementation("media.kamel:kamel-decoder-image-vector:1.0.9")
implementation("media.kamel:kamel-decoder-svg-batik:1.0.9")
implementation("media.kamel:kamel-decoder-svg-std:1.0.9")
implementation("media.kamel:kamel-decoder-animated-image:1.0.9") // .gif support
// ...
}
}
jvmMain {
dependencies {
// optional modules (choose what you need and add them to your kamel config)
implementation("media.kamel:kamel-fetcher-resources-jvm:1.0.9")
// ...
}
}
androidMain {
dependencies {
// optional modules (choose what you need and add them to your kamel config)
implementation("media.kamel:kamel-fetcher-resources-android:1.0.9")
// ...
}
}
}
}
Granular Setup: Ktor HttpClient Engine
When using kamel-image ktor engines are not included per target.
In order to fetch remote images you also must ensure you add your own ktor engine for each target.
You can find the available engines here.
Usage
Loading an image resource
To load an image asynchronously, you can use asyncPainterResource composable, it can load
images from different data sources:
// String
asyncPainterResource(data = "https://www.example.com/image.jpg")
asyncPainterResource(data = "file:///path/to/image.png")
// Ktor Url
asyncPainterResource(data = Url("https://www.example.com/image.jpg"))
// URI
asyncPainterResource(data = URI("https://www.example.com/image.png"))
// File (JVM, Native)
asyncPainterResource(data = File("/path/to/image.png"))
// File (JS)
asyncPainterResource(data = File(org.w3c.files.File(arrayOf(blob), "/path/to/image.png")))
// URL
asyncPainterResource(data = URL("https://www.example.com/image.jpg"))
// and more...
asyncPainterResource can be used to load SVG, XML, JPEG, and PNG by default depending on the
platform implementation.
asyncPainterResource returns a Resource<Painter> object which can be used to display the image
using KamelImage composable.
Platform specific implementations
Since there isn't any shared resource system between Android and Desktop, some implementations (e.g. fetchers and mappers) are only available for a specific platform:
Desktop only implementations
To load an image file from desktop application resources, you have to add resourcesFetcher to
the KamelConfig:
val desktopConfig = KamelConfig {
takeFrom(KamelConfig.Default)
// Available only on Desktop.
resourcesFetcher()
// Available only on Desktop.
// An alternative svg decoder
batikSvgDecoder()
}
Assuming there's an image.png file in the /resources directory in the project:
CompositionLocalProvider(LocalKamelConfig provides desktopConfig) {
asyncPainterResource("image.png")
}
Android only implementations
To load an image file from android application resources, you have to add resourcesFetcher
and resourcesIdMapper to the KamelConfig:
val context: Context = LocalContext.current
val androidConfig = KamelConfig {
takeFrom(KamelConfig.Default)
// Available only on Android.
resourcesFetcher(context)
// Available only on Android.
resourcesIdMapper(context)
}
Assuming there's an image.png file in the /res/raw directory in the project:
CompositionLocalProvider(LocalKamelConfig provides androidConfig) {
asyncPainterResource(R.raw.image)
}
Configuring an image resource
asyncPainterResource supports configuration using a trailing lambda:
val painterResource: Resource<Painter> = asyncPainterResource("https://www.example.com/image.jpg") {
// CoroutineContext to be used while loading the image.
coroutineContext = Job() + Dispatcher.IO
// Customizes HTTP request
requestBuilder { // this: HttpRequestBuilder
header("Key", "Value")
parameter("Key", "Value")
cacheControl(CacheControl.MAX_AGE)
}
}
Displaying an image resource
KamelImage is a composable function that takes a Resource<Painter> object, display it
and provide extra functionality:
KamelImage(
resource = painterResource,
contentDescription = "Profile",
)
KamelImage can also be used to get the exception using onFailure,
and progress using onLoading parameters, to display a snackbar or a progress indicator,
depending on the case:
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Box {
KamelImage(
resource = painterResource,
contentDescription = "Profile",
onLoading = { progress -> CircularProgressIndicator(progress) },
onFailure = { exception ->
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = exception.message.toString(),
actionLabel = "Hide",
duration = SnackbarDuration.Short
)
}
}
)
SnackbarHost(hostState = snackbarHostState, modifier = Modifier.padding(16.dp))
}
You can also provide your own custom implementation using a simple when expression:
when (val resource = asyncPainterResource("https://www.example.com/image.jpg")) {
is Resource.Loading -> {
Text("Loading...")
}
is Resource.Success -> {
val painter: Painter = resource.value
Image(painter, contentDescription = "Profile")
}
is Resource.Failure -> {
log(resource.exception)
val fallbackPainter = painterResource("/path/to/fallbackImage.jpg")
Image(fallbackPainter, contentDescription = "Profile")
}
}
Crossfade animation
You can enable, disable or customize crossfade (fade-in) animation through the animationSpec
parameter. Setting animationSpec to null will disable the animation:
KamelImage(
resource = imageResource,
contentDescription = "Profile",
// null by default
animationSpec = tween(),
)
Configuring Kamel
Related Skills
healthcheck
349.2kHost security hardening and risk-tolerance configuration for OpenClaw deployments
imsg
349.2kiMessage/SMS CLI for listing chats, history, and sending messages via Messages.app.
xurl
349.2kA CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.
qqbot-channel
349.2kQQ 频道管理技能。查询频道列表、子频道、成员、发帖、公告、日程等操作。使用 qqbot_channel_api 工具代理 QQ 开放平台 HTTP 接口,自动处理 Token 鉴权。当用户需要查看频道、管理子频道、查询成员、发布帖子/公告/日程时使用。
