RecyclerView Lazy Loading (Universal Image Loader)
A

2

6

Using Android Universal Image Loader and RecyclerView to asynchronously load images, I'm getting the same error as other people, where the images get mixed up; until they have all loaded an are cached.

Code for the adapter:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.media.Image;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;

import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import com.nostra13.universalimageloader.utils.MemoryCacheUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import romina.theftest.connectivity.ImgDownloader;

/**
 * Created by romin on 18/1/2016.
 */
public class ProductRecyclerViewAdapter extends RecyclerView.Adapter {
    private List<Product> mValues;
    private Context mContext;
    private View.OnClickListener mListener;
    // Allows to remember the last item shown on screen
    private int lastPosition = -1;
    private final String OLD_DOMAIN = "";
    private final String NEW_DOMAIN = "";

    public ProductRecyclerViewAdapter(Context mContext, View.OnClickListener mListener) {
        this.mContext = mContext;
        this.mListener = mListener;
    }

    public ProductRecyclerViewAdapter(List<Product> mValues, Context mContext, View.OnClickListener mListener) {
        this(mContext, mListener);
        this.mListener = mListener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.product_list_item, parent, false);

        Log.d(ProductRecyclerViewAdapter.class.getSimpleName(), "onCreateViewHolder");
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ViewHolder actualViewHolder = (ViewHolder) holder;
        actualViewHolder.mItem = mValues.get(position);
        actualViewHolder.mIdView.setText("" + mValues.get(position).getName());
        Log.d(ProductRecyclerViewAdapter.class.getSimpleName(), "onBindViewHolder pos " + position);

        ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance
        final String finalImgURL = mValues.get(position).getImgURL().toString().replaceAll(OLD_DOMAIN, NEW_DOMAIN);
            imageLoader.displayImage(finalImgURL, actualViewHolder.mImgView);

        setAnimation(actualViewHolder.mContentView, position);
    }

    public void setDataSet(List<Product> newValues) {
        mValues = newValues;
        notifyDataSetChanged();
    }


    @Override
    public int getItemCount() {
        return mValues == null ? 0 : mValues.size();
    }

    /**
     * Here is the key method to apply the animation
     */
    private void setAnimation(View viewToAnimate, int position) {
        // If the bound view wasn't previously displayed on screen, it's animated
        if (position > lastPosition) {
            Animation animation = AnimationUtils.loadAnimation(mContext, android.R.anim.slide_in_left);
            viewToAnimate.startAnimation(animation);
            lastPosition = position;
        }
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public final View mView;
        public final TextView mIdView;
        public final ImageView mImgView;
        public final TextView mContentView;
        public Product mItem;

        public ViewHolder(View view) {
            super(view);
            mView = view;
            mIdView = (TextView) view.findViewById(R.id.product_quantity_title);
            mContentView = (TextView) view.findViewById(R.id.product_quantity_title);
            mImgView = (ImageView) view.findViewById(R.id.product_quantity_image);

            mView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (null != mListener) {
                        mListener.onClick(v);
                    }
                }
            });
        }

        @Override
        public String toString() {
            return super.toString() + " '" + mContentView.getText() + "'";
        }
    }
}

I know it has to be something in onBindViewHolder , since it's called for updating every view but I'm not updating the ImageView properly.

It doesn't have to do with the library. The same behaviour was happening when doing lazy loading without caching the images. The error is because I don't know how to update the ImageView in onBindViewHolder .

Thanks!

Anticipatory answered 19/1, 2016 at 1:16 Comment(3)
Hi, How you fix your issue? please share solution.I am having same issue. when i scroll Recyleview then UIL mixing-up/reloading different images with already loaded previous images.Menopause
@JasbirBhinder I did something similar to the accepted answer, but I now realize that it's better to use Picasso for it.Anticipatory
thanks for the suggestionMenopause
A
5

You need to make sure you init the ImageLoader only once in the App. Create a class and extend it with Application and then put in the AndroidManifest.xml like:

<application
        android:name=".App"
.../>

Application class

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // UNIVERSAL IMAGE LOADER SETUP
        DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
                .resetViewBeforeLoading(true)
                .cacheOnDisk(true)
                .cacheInMemory(true)
                .imageScaleType(ImageScaleType.EXACTLY)
                .displayer(new FadeInBitmapDisplayer(300))
                .build();

        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
                .defaultDisplayImageOptions(defaultOptions)
                .memoryCache(new WeakMemoryCache())
                .diskCacheSize(100 * 1024 * 1024)
                .build();

        ImageLoader.getInstance().init(config);
        // END - UNIVERSAL IMAGE LOADER SETUP
    }
}

For onBindViewHolder which you want to know:

@Override
public void onBindViewHolder(final CategoryHolder holder, final int i) {

    holder.categoryImage.setImageBitmap(null);

    if (mRow.get(i).getImage() != null && !mRow.get(i).getImage().equals("")) {
        final File image = DiskCacheUtils.findInCache(mRow.get(i).getImage(), imageLoader.getDiskCache());
        if (image!= null && image.exists()) {
            Picasso.with(getActivity()).load(image).fit().centerCrop().into(holder.categoryImage);
        } else {
            imageLoader.loadImage(mRow.get(i).getImage(), new ImageLoadingListener() {
                @Override
                public void onLoadingStarted(String s, View view) {
                    holder.categoryImage.setImageBitmap(null);
                }

                @Override
                public void onLoadingFailed(String s, View view, FailReason failReason) {

                }

                @Override
                public void onLoadingComplete(String s, View view, final Bitmap bitmap) {
                    Picasso.with(getActivity()).load(s).fit().centerCrop().into(holder.categoryImage);

                }

                @Override
                public void onLoadingCancelled(String s, View view) {

                }
            });
        }
    }else {
        holder.categoryImage.setImageBitmap(null);
    }

    holder.categoryName.setText(mRow.get(i).getName().toUpperCase());

}
Alexanderalexandr answered 19/1, 2016 at 5:41 Comment(8)
Though it worked (thanks!) do you know what is the correct way to update views in onBindViewHolder ? I know I will face the same issue with any other view sooner or later; but all examples I see are with TextViews, which are rather simple to update.Anticipatory
You need to init Imageloader only once in the Application class. I will update the code in some time.Alexanderalexandr
Picasso & UIL together? could you please enlighten us and tell us why did you use Picasso & UIL together?Krissie
@k0sh I am using UIL for caching and Picasso for Complex image transformations with minimal memory use. If it is wrong then please help me correct, as I am still exploring this area.Alexanderalexandr
I didn't use Picasso, but @Alexanderalexandr 's code in onCreate worked.Anticipatory
Can someone explain why this answer is at -1? It appears to have worked for the OP, but I don't know if there is any reason I shouldn't be using it.Moonwort
@Jam The reason why it has -1 is because I am using 2 libraries, UIL and Picasso. Though in my implementation I am usinh UIL for image downloading and Picasso to handle image resizing recycling etc. I am ofcourse disabling and clearing picasso cache, because Picasso can store image in its own cache.Alexanderalexandr
Why do you work with 2 different cache managers? Picasso and UIL? I doesn't make any sense.Rump
S
1

I believe the error is due to multiple instances of image loader:

ImageLoader imageLoader = ImageLoader.getInstance(); 

Try creating a single instance of the same for the adapter. Declare it with other class variables. You are creating multiple instances of image loader by calling it in onBindViewHolder().

Seleucia answered 19/1, 2016 at 13:34 Comment(2)
Hey, thanks for responding. The error wasn't caused by this, but you're right I shouldn't be getting the instance inside that method. Thanks!Anticipatory
You are right on the multiple instance, but that wont give any error. It's just that it will be called every time RecyclerView is scrolled.Alexanderalexandr

© 2022 - 2024 — McMap. All rights reserved.