bumptech / glide

An image loading and caching library for Android focused on smooth scrolling
https://bumptech.github.io/glide/
Other
34.68k stars 6.12k forks source link

Jetpack Compose Support #4459

Closed theapache64 closed 2 years ago

theapache64 commented 3 years ago

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

Expected Usage (minimum)

setContent {
    GlideImage(url)
}
open-schnick commented 3 years ago

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

LachlanMcKee commented 3 years ago

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

anhanh11001 commented 3 years ago

@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 3 years ago

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.

rurimo commented 3 years ago

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

kurtsson commented 3 years ago

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)
  }
}
sanjeevirajm commented 3 years ago

@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

kurtsson commented 3 years ago

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

bvitaliyg commented 2 years ago

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

blasiusneri commented 2 years ago

will Glide has support for Jetpack compose?

sanjeevirajm commented 2 years ago

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

ndriqimh commented 2 years ago

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

sanjeevirajm commented 2 years ago

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

ndriqimh commented 2 years ago

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

sanjeevirajm commented 2 years ago

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

mykola-dev commented 2 years ago

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

jaredsburrows commented 2 years ago

@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

SidoPillai commented 2 years ago

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 2 years ago

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.