Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetpack Compose Support #4459

Closed
theapache64 opened this issue Jan 4, 2021 · 19 comments
Closed

Jetpack Compose Support #4459

theapache64 opened this issue Jan 4, 2021 · 19 comments

Comments

@theapache64
Copy link

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

Expected Usage (minimum)

setContent {
    GlideImage(url)
}
@open-schnick
Copy link

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

@LachlanMcKee
Copy link

LachlanMcKee commented Jul 14, 2021

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

@anhanh11001
Copy link

anhanh11001 commented Jul 27, 2021

@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
Copy link
Collaborator

sjudd commented Jul 27, 2021

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
Copy link

rurimo commented Jul 31, 2021

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

@kurtsson
Copy link

kurtsson commented Nov 11, 2021

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
Copy link

@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
Copy link

@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
Copy link

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

@blasiusneri
Copy link

will Glide has support for Jetpack compose?

@sanjeevirajm
Copy link

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
Copy link

@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
Copy link

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
Copy link

ndriqimh commented Jun 1, 2022

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
Copy link

@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
Copy link

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

@jaredsburrows
Copy link
Contributor

@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

@SidoPillai
Copy link

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
Copy link
Collaborator

sjudd commented Sep 27, 2022

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 sjudd closed this as completed Sep 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests