bumptech/glide

Jetpack Compose Support

theapache64 opened this issue ยท 19 comments

It'd be great if Glide can support Jetpack compose with a composable like GlideImage

Expected Usage (minimum)

setContent {
    GlideImage(url)
}

It looks like this has been deprecated. It would be good to port this behaviour and support it going forward within this project instead :)

google/accompanist#550

@sjudd Hey, I believe you're one of the maintainers of Glide. Is it a planned feature? Does Glide's team want help from the community on this? Jetpack Compose is going to stable soon, I believe later this month or next month

sjudd commented

I'm totally happy to get contributions here. I personally don't have much opportunity to use compose or Kotlin at work right now, so someone familiar with the space would be great.

Probably it would make sense to convert one of the existing sample apps to kotlin/compose, then write the integration library.

This library looks like an alternative...
https://github.com/skydoves/landscapist

Glide works fine with compose using this little helper method:

@Composable
fun loadPicture(url: String, placeholder: Painter? = null): Painter? {

  var state by remember {
    mutableStateOf(placeholder)
  }

  val options: RequestOptions = originalSizeStrategy
  val context = LocalContext.current
  val result = object : CustomTarget<Bitmap>() {
    override fun onLoadCleared(p: Drawable?) {
      state = placeholder
    }

    override fun onResourceReady(
      resource: Bitmap,
      transition: Transition<in Bitmap>?,
    ) {
      state = BitmapPainter(resource.asImageBitmap())
    }
  }
  try {
    Glide.with(context)
      .asBitmap()
      .load(url)
      .apply(options)
      .into(result)
  } catch (e: Exception) {
    // Can't use LocalContext in Compose Preview
  }
  return state
}
@Composable
fun ImageItem() {
  val painter = loadPicture(
    url = item.image.fragments.image.href,
    placeholder = painterResource(id = R.drawable.tc_ic_no_image)
  )
  if (painter != null) {
    Image(painter = painter)
  }
}

@kurtsson it might cause out of memory exception, you can use BoxWithConstraints to get available size of composable and reduce resolution of bitmap before setting it to state

@sanjeevirajm Good point, it's more a proof of concept than a solution every possible outcome. But if you trust your indata you shouldn't have to worry about that right?

Any updates on this issue? Do you have plans to implement it?

will Glide has support for Jetpack compose?

Created a POC.
https://github.com/sanjeevirajm/GlideCompose/

It does these two things,

Properly cancels the image request
Gets the target size using BoxWithConstraints and loads image only for the target size

@sanjeevirajm I had problem with GlideImage using so much memory when fetching images from Firebase Storage and was causing lags especially when used in LazyColumn. This implementation works great. One thing that could be improved is the blinking of the image. If you could look into that would be great.

I have used it in LazyColumn. It works well. But not sure about large images. Ideally it shouldn't consume much memory since it adds glide target size based on the composable size.
Try setting width and height in GlideImage function call. Like
GlideImage(
modifier = Modifier.width(100.dp).height(100.dp)
...
)

I have used it in LazyColumn. It works well. But not sure about large images. Ideally it shouldn't consume much memory since it adds glide target size based on the composable size. Try setting width and height in GlideImage function call. Like GlideImage( modifier = Modifier.width(100.dp).height(100.dp) ... )

My bad for not explaining well. GlideImage was another library that was causing heavy memory usage. This implementation of yours is great fixed all that. A minor improvement is that flashing that is happening when you load images or if you go back to a screen where they were loaded the just flash like is loading them again

@ndriqimh Try passing a placeholder value and check whether the issue persists. If you don't have any placeholder drawable, pass an empty transparent drawable. I think it will work fine.

guys, use Coil library. it supports compose very well. you can even use composables as loading/error placeholders

@kurtsson @sanjeevirajm I adopted this solution here: jaredsburrows/android-gif-search@5690523.

Code:

class ImageService @Inject constructor(@ApplicationContext private val context: Context) {
  /** Compose views */
  fun loadGif(
    imageUrl: String,
    thumbnailUrl: String,
    onResourceReady: (GifDrawable?) -> Unit,
    onLoadFailed: () -> Unit,
  ) {
    loadGif(imageUrl)
      .override(SIZE_ORIGINAL, SIZE_ORIGINAL)
      .thumbnail(loadGif(thumbnailUrl))
      .into(object : CustomTarget<GifDrawable>() {
        override fun onLoadFailed(errorDrawable: Drawable?) {
          super.onLoadFailed(errorDrawable)
          onLoadFailed.invoke()
        }

        override fun onLoadCleared(placeholder: Drawable?) {
          onLoadFailed.invoke()
        }

        override fun onResourceReady(
          resource: GifDrawable,
          transition: Transition<in GifDrawable>?,
        ) {
          onResourceReady.invoke(resource)
        }
      })
  }

  /** ImageViews */
  fun loadGif(
    imageUrl: String,
    thumbnailUrl: String,
    imageView: ImageView,
    onResourceReady: () -> Unit,
    onLoadFailed: (GlideException?) -> Unit,
  ) {
    loadGif(imageUrl)
      .override(SIZE_ORIGINAL, SIZE_ORIGINAL)
      .thumbnail(loadGif(thumbnailUrl))
      .listener(
        object : RequestListener<GifDrawable> {
          override fun onResourceReady(
            resource: GifDrawable?,
            model: Any?,
            target: Target<GifDrawable>?,
            dataSource: DataSource?,
            isFirstResource: Boolean
          ): Boolean {
            onResourceReady.invoke()
            return false
          }

          override fun onLoadFailed(
            e: GlideException?,
            model: Any?,
            target: Target<GifDrawable>?,
            isFirstResource: Boolean
          ): Boolean {
            onLoadFailed.invoke(e)
            return false
          }
        }
      )
      .into(imageView)
      .clearOnDetach()
  }

  private fun loadGif(imageUrl: String): RequestBuilder<GifDrawable> {
    return GlideApp.with(context)
      .asGif()
      .transition(withCrossFade())
      .load(imageUrl)
  }
}

See the code here: https://github.com/jaredsburrows/android-gif-example/blob/52914cd63b528b3a9365df6bfa2134ffdfa0e0d7/app/src/main/java/com/burrowsapps/example/gif/data/ImageService.kt#L22

Usage:

     composeView.setContent {
        val showProgressBar = remember { mutableStateOf(true) }
        val state = remember { mutableStateOf<GifDrawable?>(null) }

        GifTheme {
          // Load images - 'tinyGifPreviewUrl' -> 'tinyGifUrl'
          imageService.loadGif(
            imageUrl = imageInfoModel.tinyGifUrl,
            thumbnailUrl = imageInfoModel.tinyGifPreviewUrl,
            onResourceReady = { resource ->
              showProgressBar.value = false
              state.value = resource
            },
            onLoadFailed = {
              showProgressBar.value = false
              state.value = null
            },
          )

          // Show loading indicator when image is not loaded
          if (showProgressBar.value) {
            CircularProgressIndicator(
              modifier = Modifier
                .fillMaxWidth()
                .height(128.dp)
                .padding(all = 24.dp),
            )
          } else {
            Image(
              painter = rememberDrawablePainter(drawable = state.value),
              contentDescription = stringResource(id = R.string.gif_image),
              contentScale = ContentScale.Crop,
              modifier = Modifier
                .fillMaxWidth()
                .height(135.dp),
            )
          }
        }
      }

See the code here: https://github.com/jaredsburrows/android-gif-example/blob/52914cd63b528b3a9365df6bfa2134ffdfa0e0d7/app/src/main/java/com/burrowsapps/example/gif/ui/giflist/GifAdapter.kt#L73

guys, use Coil library. it supports compose very well. you can even use composables as loading/error placeholders

@mykola-dev Images load slower in Coil.

sjudd commented

An initial version is available see https://bumptech.github.io/glide/int/compose.html for how to access it. The remaining steps are to do an actual release of the alpha version and then iterate on any feedback.