question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Image flickering on notifyDataSetChanged()

See original GitHub issue

Problems cause by this bug:

  1. image flickering
  2. if you load image grid, scroll down, then scroll up, images are reloaded again
  3. first batch of images consume TWICE as much memory.

It looks like there is a bug here:

/* com.nostra13.universalimageloader.core.ImageLoader:193 */
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, configuration.maxImageWidthForMemoryCache, configuration.maxImageHeightForMemoryCache);
String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize);

First time when views is not created, it generates image cache key with screenWidth x screenHeight. And when views already created, it generates a new image cache key viewWidth x viewHeight.

Since keys is different both times, images are considered different so it loads the same image twice into memory.

So if we have a grid with 20 images, we will get:

  1. cache with 40 images
  2. grid flickering or image reloading on scrolling

It can be very easy reproduced with samples that comes with UIL:

  1. call notifyDataSetChanged one more time. or
  2. just scroll down then up.

If someone want a quick fix, replace cache key generator with static uri:

/* com.nostra13.universalimageloader.core.ImageLoader:194 */
String memoryCacheKey = uri; // MemoryCacheUtil.generateKey(uri, targetSize);

Issue Analytics

  • State:open
  • Created 10 years ago
  • Comments:36 (8 by maintainers)

github_iconTop GitHub Comments

8reactions
ousenkocommented, Jan 7, 2015

I solved this “bug” (it is definitely not) with a much simpler approach, - no need for custom helpers or solutions, just use View’s tag and ViewHolder pattern properly:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.row_layout, parent, false);
            viewHolder = new ViewHolder();

            viewHolder.title = (TextView) convertView.findViewById(R.id.row_layout_title);
            viewHolder.image = (ImageView) convertView.findViewById(R.id.row_layout_imgView);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        Item item = someAdapterArray.get(position);

        viewHolder.title.setText(item.getTitle());

        //lets prevent "blinking" by "saving URL tag" hack

        if (viewHolder.image.getTag() == null ||
                !viewHolder.image.getTag().equals(item.getImageUrl())) {

            //we only load image if prev. URL and current URL do not match, or tag is null

            ImageLoader.getInstance().displayImage(item.getImageUrl(), viewHolder.image, new SimpleImageLoadingListener());
            viewHolder.image.setTag(item.getImageUrl());
        }

        return convertView;
    }

Yeah, it’s a lot of tags, but this will prevent your ImageViews from being reloaded upon any subtle change to a ListView/GridView state.

P.S. Take NavigationDrawer open/close event as an example of such a “subtle change” - this will be followed by notifyDataSetChanged() even if no change to adapter’s underlying data occured.

2reactions
nfirexcommented, May 8, 2014

Hello!

I fixed this problem with helper-class (clear animation of ImageView and setting Bitmap from memory cache manually):

    protected static void loadImageByPath(DisplayImageOptions options, String path, ImageViewAware image, ImageLoadingListener listener) {
        ImageLoader.getInstance().cancelDisplayTask(image);
        image.getWrappedView().clearAnimation();
        image.setImageDrawable(null);

        final ImageSize size = ImageSizeUtils.defineTargetSizeForView(image, null);
        final String cacheKey = MemoryCacheUtil.generateKey(path, size);
        final List<Bitmap> cachedBitmaps = MemoryCacheUtil.findCachedBitmapsForImageUri(cacheKey, ImageLoader.getInstance().getMemoryCache());

        if (cachedBitmaps.size() > 0) {
            final Bitmap bitmap = cachedBitmaps.get(0); 

            // Yep, sometimes it is null
            if (bitmap != null) {
                if (listener != null) {
                    listener.onLoadingComplete(path, image.getWrappedView(), bitmap);
                }

                image.setImageBitmap(bitmap);

                return;
            }
        }

        ImageLoader.getInstance().displayImage(path, image, options, listener);
    }

My loading strategy is:

  1. Reset ImageView before loading (resetViewBeforeLoading(true));
  2. If I have Bitmap for item in memory cache - It will be set without fade;
  3. If I don’t have Bitmap for item in memory cache - It will be downloaded and set in ImageView with fade. That’s why that code is solution for me. My DisplayImageOptions:
final DisplayImageOptions fadeDisplayOptionsForList = new DisplayImageOptions.Builder()
    .resetViewBeforeLoading(true)
    .showImageOnLoading(R.color.color_image_loading)
    .showImageForEmptyUri(R.drawable.image_catalog_error)
    .showImageOnFail(R.drawable.image_catalog_error)
    .cacheInMemory(true)
    .cacheOnDisc(true)
    .postProcessor(null)
    .imageScaleType(ImageScaleType.EXACTLY)
    .bitmapConfig(Bitmap.Config.RGB_565)
    .displayer(new FadeInBitmapDisplayer(200))
    .build();

I use FadeInBitmapDisplayer in my DisplayImageOptions and I think that ImageLoader don’t clear Animation after setting Bitmap. When you calls Adapter.notifyDataSetChanged(), AdapterView calls View.requestLayout() that is invalidates his children and restart Animation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Avoid That RecyclerView's Views are Blinking when ...
However, when we refreshed the RecyclerView by notifyDataSetChanged() , it is blinking for a short time, especially strange on cell's inner image view....
Read more >
android - RecyclerView blinking on notifyDataSetChanged()
ok im trying the second solution , and there is an error , it says " notifyItemRangeInserted(int, int)' in 'androidx.recyclerview.
Read more >
Image flickering on notifyDataSetChanged() - Bountysource
Problems cause by this bug: 1. image flickering 2. if you load image grid, scroll down, then scroll up, images are reloaded again...
Read more >
How to fix blinking issue for RecyclerView notify changes
And you may noticed that whenever you use adapter. NotifyDataSetChanged(), adapter.notifyItemChanged(int position) etc methods then your ...
Read more >
Android – RecyclerView blinking after notifyDatasetChanged()
setHasStableIds(true) and override getItemId(int position) . Without stable IDs, after notifyDataSetChanged() , ViewHolders usually assigned to not to same ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found