Landscapist

Project Url: skydoves/Landscapist
Introduction: 🍂 Jetpack Compose image loading library that fetches and displays network images with Glide, Coil, and Fresco
More: Author   ReportBugs   
Tags:

landscapist

Google
License API Build Status Android Weekly Medium Profile

🍂 Jetpack Compose image loading library which fetches and displays network images with Glide, Coil, and Fresco

👉 Check out who's using Landscapist.

Demo projects

You can see the use cases of this library in the repositories below:

  • 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.

SNAPSHOT

See how to import the snapshot ### Including the SNAPSHOT Balloon
Snapshots of the current development version of Landscapist are available, which track the latest versions. To import snapshot versions on your project, add the code snippet below on your gradle file. Gradle repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } }
Glide

Glide

Maven Central

Add the below codes to your root build.gradle file (not your module-level build.gradle file):

allprojects {
    repositories {
        mavenCentral()
    }
}

Next, add the below dependency to your module's build.gradle file:

dependencies {
    implementation "com.github.skydoves:landscapist-glide:1.4.5"
}

Note: Landscapist-Glide includes version 4.12.0 of Glide internally. So please make sure your project is using the same Glide version or exclude the Glide dependency to adapt yours. Also, please make sure the Jetpack Compose version on the release page.

GlideImage

You can load images simply by using GlideImage composable function as the following example below:

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)
)

More Details for GlideImage

👉 Read further for more details ### Custom RequestOptions and TransitionOptions You can customize your request-options with your own RequestOptions and TransitionOptions for applying caching strategies, loading transformations like below: kotlin GlideImage( imageModel = poster.poster, requestOptions = { RequestOptions() .override(256, 256) .diskCacheStrategy(DiskCacheStrategy.ALL) .centerCrop() }, contentScale = ContentScale.Crop, modifier = modifier, alignment = Alignment.Center, ) ### Custom RequestBuilder You can request image with your own RequestBuilder, which 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. kotlin GlideImage( imageModel = poster.poster, requestBuilder = { Glide.with(LocalContext.current.applicationContext).asDrawable() }, modifier = Modifier.constrainAs(image) { centerHorizontallyTo(parent) top.linkTo(parent.top) }.aspectRatio(0.8f) ) ### LocalGlideRequestOptions You can pass the same instance of your RequestOptions down through the Composition in your composable hierarchy as following the example below: kotlin val requestOptions = RequestOptions() .override(300, 300) .circleCrop() CompositionLocalProvider(LocalGlideRequestOptions provides requestOptions) { // Loads images with the custom `requestOptions` without explicit defines. GlideImage( imageModel = ... ) }
Fresco

Coil

Maven Central
Add the below dependency to your module's build.gradle file.

dependencies {
    implementation "com.github.skydoves:landscapist-coil:<version>"
}

Note: Please make sure your project uses the same Jetpack Compose version on the release page.

CoilImage

You can load images by using the CoilImage composable function as the following example below:

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)
)

More Details for CoilImage

👉 Read further for more details ### Custom ImageRequest and ImageLoader You can load images with your own ImageRequest and ImageLoader, which provides all the necessary information for loading images like caching strategies and transformations. kotlin 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, ) ### LocalCoilImageLoader You can pass the same instance of your ImageLoader down through the Composition in your composable hierarchy as following the example below: kotlin val imageLoader = ImageLoader.Builder(context).build() CompositionLocalProvider(LocalCoilImageLoader provides imageLoader) { // This will automatically use the value of current imageLoader in the hierarchy. CoilImage( imageModel = ... ) } ## Animated Image Supports (GIF, Webp) You can load animated GIFs and WebP Images with your ImageLoader. kotlin 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 an animated image. imageLoader = { imageLoader }, shimmerParams = ShimmerParams( baseColor = background800, highlightColor = shimmerHighLight ), modifier = Modifier .fillMaxWidth() .padding(8.dp) .height(500.dp) .clip(RoundedCornerShape(8.dp)) )
Fresco

Fresco

Maven Central

Add the below dependency to your module's build.gradle file.

dependencies {
    implementation "com.github.skydoves:landscapist-fresco:<version>"
}

Note: Landscapist-Fresco includes version 2.6.0 of Fresco. So please make sure your project is using the same Fresco version or exclude the Fresco dependency to adapt yours. Also, please make sure the Jetpack Compose version on the release page.

Setup

To get started, you should set up Fresco with ImagePipelineConfig in your Application class. Generally, it's recommended initializing with OkHttpImagePipelineConfigFactory. Also, you can customize caching, networking, and thread pool strategies with your own ImagePipelineConfig. For more details, you can check out Using Other Network Layers.

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)
  }
}

FrescoImage

You can load images by using the FrescoImage composable function as the following example below:

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)
)

More Details for FrescoImage

👉 Read further for more details ### Custom ImageRequest You can load images with your own ImageRequest, which provides some necessary information for loading images like decoding strategies and resizing. kotlin 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) ### LocalFrescoImageRequest You can pass the same instance of your ImageRequest down through the Composition in your composable hierarchy as following the example below: kotlin // 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 = ... ) } ## Fresco Animated Image Support (GIF, Webp) Maven Central
Add the below dependency to your module's build.gradle file. gradle dependencies { implementation "com.github.skydoves:landscapist-fresco-websupport:<version>" } You can load animated GIFs and WebP Images with FrescoWebImage composable function. You should pass the AbstractDraweeController like the following example below: kotlin FrescoWebImage( controllerBuilder = Fresco.newDraweeControllerBuilder() .setUri(poster.gif) // GIF or Webp image url. .setAutoPlayAnimations(true), modifier = Modifier .fillMaxWidth() .height(300.dp) ) For more details, check out DraweeController, and Supported URIs for setting URI addresses. Also, you can load general images (jpeg, png, etc) which can be loaded with FrescoImage by using FrescoWebImage and your custom controller.

Custom Composables

You can build compose with your own composable functions following the three request states.

  • loading: While loading an image, the indicator will be shown up.
  • success: If succeed to load an image, the indicator will be gone and a content image will be shown.
  • failure: If fail to load an image (e.g. network error, wrong destination), an error placeholder will be shown up instead.

 GlideImage( // CoilImage, FrescoImage
   imageModel = imageUrl,
   modifier = modifier,
   // shows an indicator while 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 if fail to load an image.
   failure = {
     Text(text = "image request failed.")
   })

Also, you can customize the image content with our own composable function like the example below:

GlideImage( // CoilImage, FrescoImage
  imageModel = imageUrl,
  // draw a resized image.
  success = { imageState ->
    imageState.imageBitmap?.let {
      Image(
        bitmap = it,
        modifier = Modifier
          .width(128.dp)
          .height(128.dp))
    }
  },
  loading = { 
    // do something 
  })

Note: You can also use the custom Composables for CoilImage and FrescoImage.

Preview on Android Studio

Landscapist supports preview mode for each image library; Glide, Coil, and Fresco. You can show the preview image on your editor with a previewPlaceholder parameter as following:

GlideImage(
  imageModel = poster.poster,
  modifier = Modifier.aspectRatio(0.8f),
  previewPlaceholder = R.drawable.poster
)

Note: You can also use the the previewPlaceholder parameter for CoilImage and FrescoImage.

Shimmer Effect

You can implement a shimmering effect while loading an image by using the ShimmerParams parameter as following the example below:

GlideImage( // CoilImage, FrescoImage
   imageModel = imageUrl,
   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.")
   })

Note: You can also use the Shimmer effect for CoilImage and FrescoImage.

Circular Reveal Animation

You can implement the circular reveal animation while drawing images with circularRevealedEnabled attribute as true.

GlideImage( // CoilImage, FrescoImage
  imageModel = imageUrl,
  // Crop, Fit, Inside, FillHeight, FillWidth, None
  contentScale = ContentScale.Crop,
  // shows an image with a circular revealed animation.
  circularRevealedEnabled = true,
  // 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)
)

The default value of the circularRevealedEnabled is false.

Note: You can also use the Circular Revewal Animation for CoilImage and FrescoImage.

Palette

You can extract major (theme) color profiles with BitmapPalette. You can check out Extract color profiles to see which kinds of colors can be extracted.

var palette by remember { mutableStateOf<Palette?>(null) }

GlideImage( // CoilImage, FrescoImage also can be used.
  imageModel = imageUrl,
  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, you can customize attributes of BitmapPalette like the example below:

var palette by remember { mutableStateOf<Palette?>(null) }

GlideImage( // CoilImage, FrescoImage also can be used.
  imageModel = imageUrl,
  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
    }
  )
)

Note: You can also use the Palette for CoilImage and FrescoImage.

Who's using Landscapist?

If your project uses Landscapist, please let me know by creating a new issue! 🤗

Twitter for Android

twitter

Inspiration

This library was 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.

Find this repository useful? :heart:

Support it by joining stargazers for this repository. :star:
Also follow me for my next creations! 🤩

License

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.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools