Compose
About
Jetpack Compose is Android’s modern toolkit for building native UI. This library integrates with Compose to allow you to load images in your Compose apps with Glide in a performant manner.
Status
Glide’s Compose integration is in beta. Please submit bugs if you encounter any and/or feature requests to Glide’s Github Issues. The API is close to final. We’ll remove the ability to use subcompositions as placeholders. We may replace GlideSubcomposition
with a custom Modifier
. If we do so, we will deprecate GlideSubcomposition
first so that you have time to migrate.
How do I include the Compose integration library?
The Compose integration doesn’t include any components, so you don’t need any changes to your AppGlideModule
to use it.
Instead, just add a Gradle dependency on the Compose integration library:
implementation "com.github.bumptech.glide:compose:1.0.0-beta01"
Usage
See Glide’s Gallery sample app for a small application that uses Compose and Glide’s Compose integration. See the Dokka page for detailed API documentation.
GlideImage
The primary integration point between Compose and Glide is GlideImage
. GlideImage
is meant to similarly to Compose’s Image function except that it uses Glide to asynchronously load images.
Simple use cases of GlideImage can include just a Model and a content description:
GlideImage(model = myUrl, contentDescription = getString(R.id.picture_of_cat))
You can supply a custom Modifier
to customize how GlideImage
is rendered:
GlideImage(
model = myUrl,
contentDescription = getString(R.id.picture_of_cat),
modifier = Modifier.padding(padding).clickable(onClick = onClick).fillParentMaxSize(),
)
You can also provide the alignment
, contentScale
, colorFilter
, and alpha
parameters that have identical defaults and function identically to the same parameters in Compose’s Image.
To configure the Glide load, you can provide a RequestBuilderTransformation
function. The function will be passed a RequestBuilder
that already has load()
called on it with your given model. You can then customize the request with any normal Glide option except for Transitions (see below for details).
GlideImage(
model = myUrl,
contentDescription = getString(R.id.picture_of_cat),
modifier = Modifier.padding(padding).clickable(onClick = onClick).fillParentMaxSize(),
) {
it
.thumbnail(
requestManager
.asDrawable()
.load(item.uri)
.signature(signature)
.override(THUMBNAIL_DIMENSION)
)
.signature(signature)
}
Placeholders
Glide supports three types of placeholders for Compose:
- Drawables
- Android resource IDs
- Painters
To specify a placeholder, use the loading
and failure
parameters in GlideImage
. Placeholders are required to be one of Glide’s Placeholder
classes:
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.integration.compose.placeholder
// Drawable
GlideImage(model = myUrl, loading = placeholder(myDrawable))
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.integration.compose.placeholder
// Resource ID
GlideImage(model = myUrl, failure = placeholder(R.drawable.my_drawable))
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.integration.compose.placeholder
// Painter
GlideImage(model = myUrl, loading = placeholder(ColorPainter(Color.Red)))
If you set a placeholder on GlideImage
directly and on RequestBuilder
via requestBuilderTransform
, the placeholder set on GlideImage
will take precedence.
Sizing
As with Glide’s View integration, Glide’s Compose integration will attempt to determine the size of your Composable and use that to load an appropriately sized image. That can only be done efficiently if you provide a Modifier
that restricts the size of the Composable. If Glide determines that either the width or the height of the Composable is unbounded, it will use Target.SIZE_ORIGINAL
, which can lead to excessive memory usage.
Whenever possible, make sure you either set a Modifier
with a bounded size or provide an override()
size to your Glide request. In addition to saving memory, loads from the disk cache will also be faster if your size is smaller.
Transitions
GlideImage
as of version alpha5
supports a Compose specific Transition
API. Transitions must be specified in GlideImage
using the Compose API. Transitions specified in RequestBuilder
will be ignored.
To cross fade from the placeholder you’ve set (if any) to the image when the load completes, use the built in CrossFade
transition:
import com.bumptech.glide.integration.compose.CrossFade
GlideImage(uri, contentDescription, transition = CrossFade)
You can also write your own custom transition. If you do, please let me know by filing an issue. There’s more we can do to make this easier.
For now your best bet is to take a look at the existing CrossFade
and follow a similar pattern: https://github.com/bumptech/glide/blob/2ae4effcf7bad2131a54423d203c2d124257fc04/integration/compose/src/main/java/com/bumptech/glide/integration/compose/Transition.kt#L99
Recomposition and state changes
In some rare cases, you might want to change your composition based on the state of a Glide load. You virtually never want to do this in any kind of scrolling list because doing so will incur several recompositions per image load, causing a significant amount of jank. As a rule if you can avoid using this API, you should.
That said, if you do need to recompose on state changes, you can use GlideSubcomposition
:
import com.bumptech.glide.integration.compose.GlideSubcomposition
GlideSubcomposition(item.uri, modifier, requestBuilderTransform = {
it.thumbnail(preloadRequestBuilder)
}) {
// state comes from GlideSubcompositionScope
when (state) {
RequestState.Failure -> TODO()
RequestState.Loading -> TODO()
// painter also comes from GlideSubcompositionScope
is RequestState.Success -> Image(painter, contentDescription = null)
}
}
If you have a use case that requires this API, please let me know by filing an issue. Another likely option here is to expose a Modifier
based API where you could compose a custom Modifier
with Glide’s and monitor Glide’s state changes via that custom Modifier
. This is not yet implemented.
GlideLazyListPreloader
GlideLazyListPreloader uses Compose’s LazyListState
to determine the direction the user is scrolling and preload images in the direction of scroll. Preloading, especially when used with relatively small image sizes in combination with Glide’s thumbnail API, can dramatically improve the UX of horiztonally or vertically scrolling UIs.
Using the preloader looks like this:
@Composable
fun DeviceMedia(mediaStoreData: List<MediaStoreData>) {
val state = rememberLazyListState()
LazyRow(state = state) {
items(mediaStoreData) { mediaStoreItem ->
// Uses GlideImage to display a MediaStoreData object
MediaStoreView(mediaStoreItem, requestManager, Modifier.fillParentMaxSize())
}
}
GlideLazyListPreloader(
state = state,
data = mediaStoreData,
size = THUMBNAIL_SIZE,
numberOfItemsToPreload = 15,
fixedVisibleItemCount = 2,
) { item, requestBuilder ->
requestBuilder.load(item.uri).signature(item.signature())
}
}
@Composable
fun MediaStoreView(item: MediaStoreData, requestManager: RequestManager, modifier: Modifier) {
val signature = item.signature()
GlideImage(
model = item.uri,
contentDescription = item.displayName,
modifier = modifier,
) {
it
// This thumbnail request exactly matches the request in GlideLazyListPreloader
// so that the preloaded image can be used here and display more quickly than
// the primary request.
.thumbnail(
requestManager
.asDrawable()
.load(item.uri)
.signature(signature)
.override(THUMBNAIL_DIMENSION)
)
.signature(signature)
}
}
state
is a LazyListState
that you can obtain using standard Compose APIs. data
is the List of model objects that you’re displaying. size
is the size of the image that you want to preload. This must exactly match at least one size provided to a Request or thumbnail Request used by GlideImage
to display the item. numberOfItemsToPreload
is total number of items you want to try to keep in memory ahead of the user’s position as they scroll. You may need some performance testing to find the right balance. Too high of a number and you may exceed the memory cache size. Too low and you won’t be able to keep up with scrolling. fixedVisibleItemCount
is a guess of how many items you think will typically be visible on the screen at once.
Finally you can provide a PreloadRequestBuilderTransform
, which will give you one object from the data
list at a time to create a Glide request for. size
will be applied for you automatically via Glide’s override API. So you only need to specify any additional components of the load that are required to make the request exactly match the load (or at least one thumbnail load) in the corresponding GlideImage
.
As with much of Glide’s Compose API, there are likely to be some API changes to this class. In particular we’d like to simplify the process of specifying the request so that the request options are not duplicated, once in GlideLazyListPreloader
and again in GlideImage
.