π Jetpack Compose image loading library which can fetch and display network images using Glide, Coil, Fresco
You can see the use cases of this library in the below repositories.
- DisneyCompose - π§Έ A demo Disney app using Jetpack Compose and Hilt based on modern Android tech-stacks and MVVM architecture.
- MovieCompose - π A demo movie app using Jetpack Compose and Hilt based on modern Android tech stacks.
Add below codes to your root build.gradle
file (not your module build.gradle file).
allprojects {
repositories {
mavenCentral()
}
}
Also add a dependency code to your module's build.gradle
file.
dependencies {
implementation "com.github.skydoves:landscapist-glide:1.3.5"
}
We can request and load images simply using a GlideImage
composable function.
GlideImage(
imageModel = imageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularReveal = CircularReveal(duration = 250),
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
We can customize our request options using RequestOptions and TransitionOptions for applying caching strategies, loading transformations.
GlideImage(
imageModel = poster.poster,
requestOptions = RequestOptions()
.override(256, 256)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.centerCrop(),
contentScale = ContentScale.Crop,
modifier = modifier,
alignment = Alignment.Center,
)
Also we can request image by passing a RequestBuilder. RequestBuilder is the backbone of the request in Glide and is responsible for bringing your options together with your requested url or model to start a new load.
GlideImage(
imageModel = poster.poster,
requestBuilder = Glide.with(LocalContext.current.applicationContext).asDrawable(),
modifier = Modifier.constrainAs(image) {
centerHorizontallyTo(parent)
top.linkTo(parent.top)
}.aspectRatio(0.8f)
)
We can provide the same instance of the RequestOptions
in the composable hierarchy.
// customize the RequestOptions as needed
val requestOptions = RequestOptions()
.override(300, 300)
.circleCrop()
CompositionLocalProvider(LocalGlideRequestOptions provides requestOptions) {
// This will automatically use the value of current RequestOptions in the hierarchy.
GlideImage(
imageModel = ...
)
}
We can create our own composable functions following requesting states.
Here is an example that shows a progress indicator when loading an image,
After complete requesting, the indicator will be gone and a content image will be shown.
If the request failed (e.g. network error, wrong destination), error text will be shown.
GlideImage(
imageModel = poster.poster,
modifier = modifier,
// shows a progress indicator when loading an image.
loading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
// shows an error text message when request failed.
failure = {
Text(text = "image request failed.")
})
Add a dependency code to your module's build.gradle
file.
dependencies {
implementation "com.github.skydoves:landscapist-coil:<version>"
}
We can request and load images simply using a CoilImage
composable function.
CoilImage(
imageModel = imageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularReveal = CircularReveal(duration = 250),
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
We can customize request options using ImageRequest and ImageLoader for providing all the necessary information for loading images like caching strategies and transformations.
CoilImage(
imageRequest = ImageRequest.Builder(LocalContext.current)
.data(poster.poster)
.crossfade(true)
.build(),
imageLoader = ImageLoader.Builder(LocalContext.current)
.availableMemoryPercentage(0.25)
.crossfade(true)
.build(),
contentScale = ContentScale.Crop,
modifier = modifier,
alignment = Alignment.Center,
)
We can create our own composable functions following requesting states. Here is an example that shows a progress indicator when loading an image, After complete requesting, the indicator will be gone and a content image will be shown. If the request failed (e.g. network error, wrong destination), error text will be shown.
CoilImage(
imageModel = poster.poster,
modifier = Modifier.constrainAs(image) {
centerHorizontallyTo(parent)
top.linkTo(parent.top)
}.aspectRatio(0.8f),
// shows a progress indicator when loading an image.
loading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
// shows an error text message when request failed.
failure = {
Text(text = "image request failed.")
})
We can give a shimmering effect when loading images using a ShimmerParams
. We can also use ShimmerParams
in GlideImage
and FrescoImage
.
CoilImage(
imageModel = poster.poster,
modifier = modifier,
// shows a shimmering effect when loading an image.
shimmerParams = ShimmerParams(
baseColor = MaterialTheme.colors.background,
highlightColor = shimmerHighLight,
durationMillis = 350,
dropOff = 0.65f,
tilt = 20f
),
// shows an error text message when request failed.
failure = {
Text(text = "image request failed.")
})
We can provide the same instance of the ImageLoader
in the composable hierarchy.
val imageLoader = ImageLoader.Builder(context)
// customize the ImageLoader as needed
.build()
CompositionLocalProvider(LocalCoilImageLoader provides imageLoader) {
// This will automatically use the value of current imageLoader in the hierarchy.
CoilImage(
imageModel = ...
)
}
Landscapist-coil supports animated GIF and WebP Images using ImageLoader
.
val context = LocalContext.current
val imageLoader = ImageLoader.Builder(context)
.componentRegistry {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder(context))
} else {
add(GifDecoder())
}
}
.build()
CoilImage(
imageModel = poster.gif, // URL of the animated images.
imageLoader = imageLoader,
shimmerParams = ShimmerParams(
baseColor = background800,
highlightColor = shimmerHighLight
),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.height(500.dp)
.clip(RoundedCornerShape(8.dp))
)
Add a dependency code to your module's build.gradle
file.
dependencies {
implementation "com.github.skydoves:landscapist-fresco:<version>"
}
We should initialize Fresco
using ImagePipelineConfig in our Application
class.
If we need to fetch images from the network, recommend using OkHttpImagePipelineConfigFactory
.
By using an ImagePipelineConfig
, we can customize caching, networking, and thread pool strategies.
Here are more references related to the pipeline config.
class App : Application() {
override fun onCreate() {
super.onCreate()
val pipelineConfig =
OkHttpImagePipelineConfigFactory
.newBuilder(this, OkHttpClient.Builder().build())
.setDiskCacheEnabled(true)
.setDownsampleEnabled(true)
.setResizeAndRotateEnabledForNetwork(true)
.build()
Fresco.initialize(this, pipelineConfig)
}
}
We can request and load images simply using a FrescoImage
composable function.
FrescoImage(
imageUrl = stringImageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularReveal = CircularReveal(duration = 250),
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
We can customize our requests using an ImageRequest that consists only of a URI, we can use the helper method ImageRequest.fromURI.
val imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setImageDecodeOptions(decodeOptions)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(ResizeOptions(width, height))
.build()
FrescoImage(
imageUrl = stringImageUrl,
imageRequest = imageRequest,
contentScale = ContentScale.Crop)
We can create our own composable functions following requesting states.
Here is an example that shows a progress indicator when loading an image,
After complete requesting, the indicator will be gone and a content image will be shown.
If the request failed (e.g. network error, wrong destination), error text will be shown.
FrescoImage(
imageUrl = stringImageUrl,
modifier = modifier,
// shows a progress indicator when loading an image.
loading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
// shows an error text message when request failed.
failure = {
Text(text = "image request failed.")
})
Also, we can customize the content image using our own composable function like below.
FrescoImage(
imageUrl = imageUrl,
// draw a resized image.
success = { frescoImageState ->
frescoImageState.imageBitmap?.let {
Image(
bitmap = it,
modifier = Modifier
.width(128.dp)
.height(128.dp))
}
},
loading = {
// do something
})
We can provide the same instance of the ImageRequest
in the composable hierarchy.
// customize the ImageRequest as needed
val imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setImageDecodeOptions(decodeOptions)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(ResizeOptions(width, height))
.build()
CompositionLocalProvider(LocalFrescoImageRequest provides imageRequest) {
// This will automatically use the value of current ImageRequest in the hierarchy.
FrescoImage(
imageurl = ...
)
}
We can extract major (theme) color profiles using BitmapPalette
. Basically, we should use BitmapPalette
for extracting the major colors from image. You can reference which kinds of colors can be extracted here.
var palette by remember { mutableStateOf<Palette?>(null) }
GlideImage( // CoilImage, FrescoImage also can be used.
imageModel = poster?.poster!!,
bitmapPalette = BitmapPalette {
palette = it
}
)
Crossfade(
targetState = palette,
modifier = Modifier
.padding(horizontal = 8.dp)
.size(45.dp)
) {
Box(
modifier = Modifier
.background(color = Color(it?.lightVibrantSwatch?.rgb ?: 0))
.fillMaxSize()
)
}
Also we can customize attributes of BitmapPalette
like the below.
var palette by remember { mutableStateOf<Palette?>(null) }
GlideImage(
imageModel = poster?.poster!!,
modifier = Modifier
.aspectRatio(0.8f),
bitmapPalette = BitmapPalette(
imageModel = poster.poster,
useCache = true,
interceptor = {
it.addFilter { rgb, hsl ->
// here edit to add the filter colors.
false
}
},
paletteLoadedListener = {
palette = it
}
)
)
Add a dependency code to your module's build.gradle
file.
dependencies {
implementation "com.github.skydoves:landscapist-fresco-websupport:<version>"
}
Fresco supports animated GIF and WebP Images using FrescoWebImage
composable function. We should pass the AbstractDraweeController
that can be created like the below.
You can reference how to build the DraweeController, and Supported URIs for setting URI addresses. Also, we can load a normal image (jpeg, png, etc) using the custom controller.
FrescoWebImage(
controllerBuilder = Fresco.newDraweeControllerBuilder()
.setUri(poster.gif) // GIF or Webp image url.
.setAutoPlayAnimations(true),
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
)
If your project uses Landscapist, let me know via creating a new issue! π€
This library is mostly inspired by Accompanist.
Accompanist is a group of libraries that contains some utilities which I've found myself copying around projects which use Jetpack Compose. Currently, it contains image loading and insets. You can get more variety and recent systems from the library maintained by Google.
Support it by joining stargazers for this repository. β
Also follow me for my next creations! π€©
Designed and developed by 2020 skydoves (Jaewoong Eum)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.