Recipes¶
This page provides guidance on how to handle some common use cases with Coil. You might have to modify this code to fit your exact requirements, but it should hopefully give you a push in the right direction!
See a common use case that isn't covered? Feel free to submit a PR with a new section.
Palette¶
Palette allows you to extract prominent colors from an image. To create a Palette
, you'll need access to an image's Bitmap
. This can be done in a number of ways:
You can get access to an image's bitmap by setting a ImageRequest.Listener
and enqueuing an ImageRequest
:
imageView.load("https://example.com/image.jpg") {
// Disable hardware bitmaps as Palette needs to read the image's pixels.
allowHardware(false)
listener(
onSuccess = { _, result ->
// Create the palette on a background thread.
Palette.Builder(result.drawable.toBitmap()).generate { palette ->
// Consume the palette.
}
}
)
}
Using a custom OkHttpClient¶
Coil uses OkHttp for all its networking operations. You can specify a custom OkHttpClient
when creating your ImageLoader
:
val imageLoader = ImageLoader.Builder(context)
// Create the OkHttpClient inside a lambda so it will be initialized lazily on a background thread.
.okHttpClient {
OkHttpClient.Builder()
.addInterceptor(CustomInterceptor())
.build()
}
.build()
Note
If you already have a built OkHttpClient
, use newBuilder()
to build a new client that shares resources with the original.
Headers¶
Headers can be added to your image requests in one of two ways. You can set headers for a single request:
val request = ImageRequest.Builder(context)
.data("https://example.com/image.jpg")
.setHeader("Cache-Control", "no-cache")
.target(imageView)
.build()
imageLoader.execute(request)
Or you can create an OkHttp Interceptor
that sets headers for every request executed by your ImageLoader
:
class RequestHeaderInterceptor(
private val name: String,
private val value: String
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.header(name, value)
.build()
return chain.proceed(request)
}
}
val imageLoader = ImageLoader.Builder(context)
.okHttpClient {
OkHttpClient.Builder()
// This header will be added to every image request.
.addNetworkInterceptor(RequestHeaderInterceptor("Cache-Control", "no-cache"))
.build()
}
.build()
Using a Memory Cache Key as a Placeholder¶
Using a previous request's MemoryCache.Key
as a placeholder for a subsequent request can be useful if the two images are the same, though loaded at different sizes. For instance, if the first request loads the image at 100x100 and the second request loads the image at 500x500, we can use the first image as a synchronous placeholder for the second request.
Here's what this effect looks like in the sample app:
Images in the list have intentionally been loaded with very low detail and the crossfade is slowed down to highlight the visual effect.
To achieve this effect, use the MemoryCache.Key
of the first request as the ImageRequest.placeholderMemoryCacheKey
of the second request. Here's an example:
// First request
listImageView.load("https://example.com/image.jpg")
// Second request (once the first request finishes)
detailImageView.load("https://example.com/image.jpg") {
placeholderMemoryCacheKey(listImageView.result.memoryCacheKey)
}
Note
Previous versions of Coil would attempt to set up this effect automatically. This required executing parts of the image pipeline synchronously on the main thread and it was ultimately removed in version 0.12.0
.
Shared Element Transitions¶
Shared element transitions allow you to animate between Activities
and Fragments
. Here are some recommendations on how to get them to work with Coil:
-
Shared element transitions are incompatible with hardware bitmaps. You should set
allowHardware(false)
to disable hardware bitmaps for both theImageView
you are animating from and the view you are animating to. If you don't, the transition will throw anjava.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps
exception. -
Use the
MemoryCache.Key
of the start image as theplaceholderMemoryCacheKey
for the end image. This ensures that the start image is used as the placeholder for the end image, which results in a smooth transition with no white flashes if the image is in the memory cache. -
Use
ChangeImageTransform
andChangeBounds
together for optimal results.
Remote Views¶
Coil does not provide a Target
for RemoteViews
out of the box, however you can create one like so:
class RemoteViewsTarget(
private val context: Context,
private val componentName: ComponentName,
private val remoteViews: RemoteViews,
@IdRes private val imageViewResId: Int
) : Target {
override fun onStart(placeholder: Drawable?) = setDrawable(placeholder)
override fun onError(error: Drawable?) = setDrawable(error)
override fun onSuccess(result: Drawable) = setDrawable(result)
private fun setDrawable(drawable: Drawable?) {
remoteViews.setImageViewBitmap(imageViewResId, drawable?.toBitmap())
AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteViews)
}
}
Then enqueue
/execute
the request like normal:
val request = ImageRequest.Builder(context)
.data("https://example.com/image.jpg")
.target(RemoteViewsTarget(context, componentName, remoteViews, imageViewResId))
.build()
imageLoader.enqueue(request)
Transforming Painters¶
Both AsyncImage
and AsyncImagePainter
have placeholder
/error
/fallback
arguments that accept Painter
s. Painters are less flexible than using composables, but are faster as Coil doesn't need to use subcomposition. That said, it may be necessary to inset, stretch, tint, or transform your painter to get the desired UI. To accomplish this, copy this Gist into your project and wrap the painter like so:
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = null,
placeholder = forwardingPainter(
painter = painterResource(R.drawable.placeholder),
colorFilter = ColorFilter(Color.Red),
alpha = 0.5f
)
)
The onDraw
can be overwritten using a trailing lambda: