Glide: [Question] How to prevent clearing an ImageView before the load is complete?

Created on 22 Oct 2017  Â·  14Comments  Â·  Source: bumptech/glide

Glide Version: 4.2.0
Integration libraries: none

Issue details: I have an ImageView that needs to be refreshed often, but each time there is a change the image flickers white (unless it is present in the memory cache).

This must be the default behavior, as I can see in the basic lifecycle of a Glide request:

  1. Cancel any in progress load for the View and/or clear out any current image and recycle its resource.
  2. Set the placeholder drawable
  3. Start the image load
  4. If it completes synchronously (from the memory cache) set the image on the View and do not animate.
  5. If it completes asynchronously, set the image on the View and animate.

Is there a way to disable the default clearing of the ImageView on step 1) until the next image is loaded in order to prevent the flicker?

The following works, but I was wondering if there is a simpler solution, for example by disabling animations. I read something about a dontAnimate() method but can't find it on 4.2.0.

Glide
    .with(thisActivity)
    .asBitmap()
    .load(someUrl)
    .into(object : SimpleTarget<Bitmap>() {
        override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>) {
            imageView.setImageBitmap(resource)
        }
    })
documentation enhancement question

Most helpful comment

you can set placeholder as current image drawable

.placeholder(view.drawable)

be aware to do it with items in RecyclerView

All 14 comments

Sounds like http://bumptech.github.io/glide/doc/transitions.html#cross-fading-across-requests.

Your solution is unsafe in that your first request may complete after your second request, which would result in you showing an incorrect image. This would occur with your solution because you're not clearing out the first request when you start the second one. Any solution where you completely disable the clearing step will have the same problem.

Instead a ViewSwitcher (or any combination of multiple Views) will let you neatly display one image and then the next only when the second image is ready.

An alternative to what Sam suggested is loading the current image as a thumbnail for the next one. By doing this, the flicker will disappear since you'll extremely likely hit the memory cache and the start the next asynchronous load. See #861 for more (it assumes that loads are uniform in size and params).

Hey @sjudd, @TWiStErRob, thank you for your answers! For now I will solve it with a ViewSwitcher.

However, it feels strange that there is no way to cancel a request without also clearing the ImageView.

Maybe this could be an enhancement. Here is a possible solution:

Glide.with(this).load(firstUrl).into(imageView);

// Overloaded method that optionally clears the view (or not) besides cancelling
boolean clearOnCancel = false;
Glide.with(this).load(nextUrl).into(imageView, clearOnCancel);

What do you think?

It's possible to add this kind of API using the RequestCoordinator interface. It's a little tricky to explain and a little tricky to get the clearing behavior correct. To allow for cross fades, you probably want to clear the original image as soon as the new image is finished loading and animating. In other cases you might actually want both images until you explicitly clear the view.

If you avoid clearing the requests until the user clears the View, you might introduce a memory leak where users just call into repeatedly with clearOnCancel false, forcing every previous request to accumulate.

If you avoid clearing the requests until the user clears the View, you might introduce a memory leak where users just call into repeatedly with clearOnCancel false, forcing every previous request to accumulate.

I'm not sure I understand this part. What I meant with clearOnCancel was the clearing of the view, not of the pending requests.

As it is now, when I make a second request on a view, it first clears any pending request AND it also clears the view. I want to prevent this last effect, the clearing of the view, which causes a flicker whenever there is a change.

Clearing the View and the Request have to go together. When the Request is cleared, Glide releases and may re-use the Bitmap for that Request. It can't be left in a View after that point.

Aaaah ok, thank you for the explanation, I wasn't aware of the implementation.

I'm closing the issue because it can be easily solved with a ViewSwitcher, as you mentioned earlier :)

But I still think there should be a way to decouple the clearing of the request and the clearing of the view.

Sure let's leave this open as an enhancement to consider in the future.

@EnricSala How do you fix this problem with ViewSwitcher? Can you show me the code ?I just have the same issue like yours. Looking forword to getting your response.Thanks!

@sjudd Can you show me some example code about the solution using ViewSwitcher to solve this issue?

@fragrantforest to solve the problem I made a class that wraps a ViewSwitcher, similar to this:

class GlideSwitcher(private val context: Context,
                    private val viewSwitcher: ViewSwitcher) {

    var imageUrl: String? = null
        set(value) {
            if (value != null) show(value) else clear()
            field = value
        }

    private fun show(url: String) {
        val current = viewSwitcher.currentView as ImageView
        val next = viewSwitcher.nextView as ImageView
        Glide.with(context)
                .load(url)
                .apply(RequestOptions()
                        .diskCacheStrategy(DiskCacheStrategy.NONE)
                        .override(current.width, current.height)
                        .centerCrop())
                .listener(listener)
                .into(next)
    }

    private fun clear() {
        val current = viewSwitcher.currentView as ImageView
        Glide.with(context).clear(current)
    }

    private val listener = object : RequestListener<Drawable> {

        override fun onResourceReady(resource: Drawable?,
                                     model: Any?,
                                     target: Target<Drawable>?,
                                     dataSource: DataSource?,
                                     isFirstResource: Boolean): Boolean {
            viewSwitcher.showNext()
            return false
        }

        override fun onLoadFailed(e: GlideException?,
                                  model: Any?,
                                  target: Target<Drawable>?,
                                  isFirstResource: Boolean): Boolean {
            Timber.e(e, "Error loading image")
            return true
        }
    }

}

To use it, just set or clear the imageUrl field.

I'm not sure if this solution is the most correct but it worked for me.

@fragrantforest to solve the problem I made a class that wraps a ViewSwitcher, similar to this:

class GlideSwitcher(private val context: Context,
                    private val viewSwitcher: ViewSwitcher) {

    var imageUrl: String? = null
        set(value) {
            if (value != null) show(value) else clear()
            field = value
        }

    private fun show(url: String) {
        val current = viewSwitcher.currentView as ImageView
        val next = viewSwitcher.nextView as ImageView
        Glide.with(context)
                .load(url)
                .apply(RequestOptions()
                        .diskCacheStrategy(DiskCacheStrategy.NONE)
                        .override(current.width, current.height)
                        .centerCrop())
                .listener(listener)
                .into(next)
    }

    private fun clear() {
        val current = viewSwitcher.currentView as ImageView
        Glide.with(context).clear(current)
    }

    private val listener = object : RequestListener<Drawable> {

        override fun onResourceReady(resource: Drawable?,
                                     model: Any?,
                                     target: Target<Drawable>?,
                                     dataSource: DataSource?,
                                     isFirstResource: Boolean): Boolean {
            viewSwitcher.showNext()
            return false
        }

        override fun onLoadFailed(e: GlideException?,
                                  model: Any?,
                                  target: Target<Drawable>?,
                                  isFirstResource: Boolean): Boolean {
            Timber.e(e, "Error loading image")
            return true
        }
    }

}

To use it, just set or clear the imageUrl field.

I'm not sure if this solution is the most correct but it worked for me.

Thank you very much !

I'm very confused about this problem! Why it need clear the view before load a new image, this doesn't make sense, and I don't think ViewSwitcher is a good solution, because if I use recyclerview or listview, the numbers of views need x2 !

Another problem that confuses me is that the load request is not synchronized! For example, I load two images, one gif, one png, and load gif is slower than loading png, so if gif is loaded first, then png is loaded before the gif is loaded, and finally gif is displayed instead of png!

In my opinion, for a view, I think all requests should be running synchronously, and when a new request arrives, the old request should be canceled!

you can set placeholder as current image drawable

.placeholder(view.drawable)

be aware to do it with items in RecyclerView

Was this page helpful?
0 / 5 - 0 ratings

Related issues

piedpiperlol picture piedpiperlol  Â·  3Comments

technoir42 picture technoir42  Â·  3Comments

billy2271 picture billy2271  Â·  3Comments

r4m1n picture r4m1n  Â·  3Comments

StefMa picture StefMa  Â·  3Comments