glide icon indicating copy to clipboard operation
glide copied to clipboard

Jetpack Compose Support

Open theapache64 opened this issue 4 years ago • 18 comments

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

Expected Usage (minimum)

setContent {
    GlideImage(url)
}

theapache64 avatar Jan 04 '21 00:01 theapache64

take a look at this: https://github.com/chrisbanes/accompanist

open-schnick avatar Feb 25 '21 08:02 open-schnick

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

https://github.com/google/accompanist/pull/550

LachlanMcKee avatar Jul 14 '21 21:07 LachlanMcKee

@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

anhanh11001 avatar Jul 27 '21 10:07 anhanh11001

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.

sjudd avatar Jul 27 '21 18:07 sjudd

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

rurimo avatar Jul 31 '21 06:07 rurimo

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 avatar Nov 11 '21 13:11 kurtsson

@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 avatar Nov 12 '21 03:11 sanjeevirajm

@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?

kurtsson avatar Nov 12 '21 07:11 kurtsson

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

bvitaliyg avatar Jan 17 '22 15:01 bvitaliyg

will Glide has support for Jetpack compose?

blasiusneri avatar Mar 04 '22 14:03 blasiusneri

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 avatar Mar 06 '22 21:03 sanjeevirajm

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

ndriqimh avatar May 31 '22 12:05 ndriqimh

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

sanjeevirajm avatar May 31 '22 22:05 sanjeevirajm

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 avatar Jun 01 '22 08:06 ndriqimh

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

sanjeevirajm avatar Jun 01 '22 10:06 sanjeevirajm

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

mykola-dev avatar Aug 05 '22 08:08 mykola-dev

@kurtsson @sanjeevirajm I adopted this solution here: https://github.com/jaredsburrows/android-gif-example/commit/5690523c6dc40c435c3d81868d89ba26d21e3663.

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

jaredsburrows avatar Aug 07 '22 18:08 jaredsburrows

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.

SidoPillai avatar Aug 25 '22 00:08 SidoPillai

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.

sjudd avatar Sep 27 '22 21:09 sjudd