Android ListView Universal Image Loader Changes Images on Scroll
Asked Answered
H

5

7

I have a problem that's been giving me grief for weeks now. I forgot about it for a while when I was updating to the new Facebook SDK, but now it's back to haunt my dreams.

I'm making basically a Facebook feed reader. It gets the JSON data from an API call and uses that to populate a ListView. Each time the custom adapter calls the getView() method, it checks to see what type of "status" it is. If it's a "photo" status, the view will have two images (a profile picture and the picture of the status update). I'm using nostra13's Universal Image Loader to load both images after inflating the ImageViews using a holder class to help recycle views. That's where I think my problem exists.

When I scroll through the list, all the profile pictures load fine and work great. But as I scroll, the status photos do a couple things:

1.) The all load the same image (I didn't have this problem before but I gave it to myself trying to fix the others).

2.) The images jump around and either disappear or appear in different views than they're supposed to.

3.) If the images load correctly and don't all show the same image, the images change as I scroll up and down, eventually all becoming the same image.

I'll post my getView() code (sorry it's kind of long).

@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    final ViewHolder holder;

    try {
        jsonObject = getItem(position);
        jsonFrom = jsonObject.getJSONObject("from");
        postType = jsonObject.getString("type");
        posterName = jsonFrom.getString("name");
        posterID = jsonFrom.getString("id");
        posterPhoto = "http://graph.facebook.com/" + posterID
                + "/picture?type=square";
        if (jsonObject.has("message")) {
            posterMessage = jsonObject.getString("message");
        } else {
            posterMessage = jsonObject.getString("story");
        }
    } catch (JSONException e) {
        e.printStackTrace();
    }

    if (postType.equals("status")) {
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.status_post_holder,null);
            holder = new ViewHolder();
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.posterName = (TextView) convertView.findViewById(R.id.posterName);
        holder.posterMessage = (TextView) convertView.findViewById(R.id.posterMessage);
        holder.posterProfilePhoto = (ImageView) convertView.findViewById(R.id.posterProfilePic);

        if (jsonObject != null) {
            holder.posterName.setText(posterName);
            if (posterMessage != null) {
                holder.posterMessage.setText(posterMessage);
            } else {
                holder.posterMessage.setText("No message for this post.");
            }
            profileImageLoader.displayImage(posterPhoto,
                    holder.posterProfilePhoto);
        }

    }

    else if (postType.equals("photo")) {
        if (convertView == null) {
            try {
                posterImageURL = jsonObject.getString("picture");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.photo_post_holder, null);
            holder = new ViewHolder();
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.posterName = (TextView) convertView.findViewById(R.id.posterName);
        holder.posterMessage = (TextView) convertView.findViewById(R.id.posterMessage);
        holder.posterProfilePhoto = (ImageView) convertView.findViewById(R.id.posterProfilePic);
        holder.posterImage = (ImageView) convertView.findViewById(R.id.posterImage);

        if (jsonObject != null) {
            holder.posterName.setText(posterName);
            if (posterPhoto != null) {
                profileImageLoader.displayImage(posterPhoto,holder.posterProfilePhoto);
            }
            if (posterImageURL != null) {
                pictureImageLoader.displayImage(posterImageURL,holder.posterImage);
            }

            if (posterMessage != null) {
                holder.posterMessage.setText(posterMessage);
            } else {
                holder.posterMessage.setText("No message for this post.");
            }
        }

    }

    else if (postType.equals("link")) {
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.status_post_holder,null);
            holder = new ViewHolder();
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.posterName = (TextView) convertView.findViewById(R.id.posterName);
        holder.posterMessage = (TextView) convertView.findViewById(R.id.posterMessage);
        holder.posterProfilePhoto = (ImageView) convertView.findViewById(R.id.posterProfilePic);

        if (jsonObject != null) {
            holder.posterName.setText(posterName);
            if (posterMessage != null) {
                holder.posterMessage.setText(posterMessage);
            } else {
                holder.posterMessage.setText("No message for this post.");
            }
            profileImageLoader.displayImage(posterPhoto,holder.posterProfilePhoto);
        }
    }

    else {
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.status_post_holder,null);
            holder = new ViewHolder();
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.posterName = (TextView) convertView.findViewById(R.id.posterName);
        holder.posterMessage = (TextView) convertView.findViewById(R.id.posterMessage);
        holder.posterProfilePhoto = (ImageView) convertView.findViewById(R.id.posterProfilePic);

        if (jsonObject != null) {
            holder.posterName.setText(posterName);
            if (posterMessage != null) {
                holder.posterMessage.setText(postType);
            } else {
                holder.posterMessage.setText("No message for this post.");
            }
            profileImageLoader.displayImage(posterPhoto,holder.posterProfilePhoto);
        }
    }

    return convertView;

}

I'm sure it has something to do with Android recycling views and being efficient, but I can't figure out where I'm making the error. It's also weird that the problem only exists for the one image view. Any help would be hot. Thanks.

UPDATE: We discovered it was an error with my JSON parsing. You have to use setTag() to retain the image specific to that line item. The adapter was trying to set a tag of null to the view, which caused the NPE.

UPDATE AGAIN: After digging some more, it looks like it's the ImageView itself that's giving a null value as if it isn't finding the correct layout. I was using two different xml layouts for the different types of posts. So now it looks like the best solution would be to use the same layout and just conditionally inflate different elements in each view depending on the status type.

Hat answered 7/12, 2012 at 3:26 Comment(1)
That solved it for me: #22727347Clerissa
C
3

please be specific during using and dealing with the image loader class. If possible, don't create more instance of the same class. Instead of use the same instance in different way.

See below adapter class and try to integrate same in your case. which will help you.

 private class OrderAdapter extends ArrayAdapter<User> {
        private ArrayList<User> items;
        Activity a;
        public ImageLoader imageLoader;

        public OrderAdapter(Context context, int textViewResourceId,ArrayList<User> items) {
            super(context, textViewResourceId, items);
            this.items = items;
            // extra
            /*if (Utility.model == null) {
                Utility.model = new FriendsGetProfilePics();
            }
            Utility.model.setListener(this);*/
        }

        /*@Override
        public boolean isEmpty() {
            // TODO Auto-generated method stub
            super.isEmpty();
            return false;
        }*/

        @Override
        public View getView(final int position, View convertView,ViewGroup parent) {
            ViewHolder holder;
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

                v = vi.inflate(R.layout.add_buddies_row_new, null);
                holder = new ViewHolder();
                imageLoader = new ImageLoader(NotificationsActivity.this);
                //--------------

                //---------------------------------------------------------------------
                // for head to head
                holder.userName = (TextView) v.findViewById(R.id.txtName);
                holder.userPic = (ImageView) v.findViewById(R.id.imgUserPic);
                holder.userCity = (TextView) v.findViewById(R.id.txtCity);
                holder.userState = (TextView) v.findViewById(R.id.txtstate);
                holder.userCountry = (TextView) v.findViewById(R.id.txtContry);
                //---------------------------------------------------------------------

                v.setTag(holder);

            }else{
                holder=(ViewHolder)v.getTag();
            }

            final User o = items.get(position);
            holder = (ViewHolder) v.getTag();

            if (o != null) {
                holder.userName.setText(o.getUserName());
                holder.userCity.setText(o.getUserCity()+", ");
                holder.userState.setText(o.getUserState()+", ");
                holder.userCountry.setText(o.getUserCountry());
                System.out.println("PIC: "+o.getUserPic());
                holder.userPic.setTag(o.getUserPic());
                imageLoader.DisplayImage(o.getUserPic(), a, holder.userPic);
            }
            return v;
        }
    }

    class ViewHolder {
        TextView userName ;
        TextView userCity;
        TextView userState;
        TextView userCountry;
        ImageView userPic ;
    }

Comment me for any dought.

Note: YOU HAVE TO DO SOME CHANGES IN ABOVE CODE AS PER YOUR REQUIREMENT.

If your issue not solve with this then let me know.

It can also happend if you are getting null data so please be confirm with getting data.

Catenate answered 7/12, 2012 at 3:41 Comment(7)
Thanks for the response. I actually had a set tag for the profile picture and it worked fine. But when I tried to use it for the status picture, it gave me a null pointer exception.Hat
You are using loader class without initiating the instance that's why i think you got null pointer exception. Can you tell me the line where you got null pointer exception ?Catenate
I used two different image loader objects, one for the profile picture and one for the post picture. I declare them and initialize them in the main method of the adapter class (FeedAdapter is the class, FeedAdapter(Activity activity, JSONArray jsonArray) is the method I declare them in.Hat
The NPE happens on whichever setTag I try first.Hat
The URL for the profile pic and the post pic are taken from the JSON data, taken from the JSON array, for the specific position in the getView method.Hat
let us continue this discussion in chatCatenate
That implementation gives me a null pointer exception at the setTag line: holder.userPic.setTag(o.getUserPic());Hat
D
0

I know it's been a while since the topic was created but my solution for the NPE of @Wenger occured because I've used findViewById on the View member called v instead of the ViewHolder:

holder.userName = (TextView) v.findViewById(R.id.txtName);

Working reference:

holder.userName = (TextView) convertView.findViewById(R.id.txtName);
Denude answered 12/11, 2013 at 21:50 Comment(0)
N
0

I had same issue with view holder pattern and universal image loader.

I was checking my url before i call universal loader display image and if url was null i dit not download image just set imageview background drawable my no image drawable. On Listview scroll my listview images were mixed.

Then i tried to do not check url if its null and just set a no url drawable to universal image loader display image options. This fixed my problem. I don't know if it's a commont solution for this problem but you can try it.

Norinenorita answered 17/9, 2014 at 11:31 Comment(0)
D
0

First of all, consider using view types - getViewTypeCount() & getItemViewType(int position) - ListView wont respect your different views - more info: how ListView recycling works.

Also, just dont parse json in getView() - do it where you are getting it.

Also (as notted above), you should init the ImageLoader only once - perfect place to do so is your Application's onCreate(). Then, you call it - ImageLoader.getInstance().loadSmth();

Also, do something with your code formatting - it's awful mess. As a tip:

        final int viewType = getItemViewType(position);
        final int layoutResId;
        switch (viewType) {
            case 0:
                layoutResId = R.layout.layout_1;
                break;
            case 1:
                layoutResId = R.layout.layout_2;
                break;
            default:
                layoutResId = R.layout.layout_3;
        }

        final View view = mInflater.inflate(layoutResId, group, false);
Deauville answered 17/9, 2014 at 12:31 Comment(0)
S
0

There are few fixes you can do

Your view holder is almost useless. You have to use it as follow

ViewHolder viewHolder;
if (view == null) {
    viewHolder = new ViewHolder();    
    viewHolder.image = (ImageView) view.findViewById(R.id.capsule_media_list_item_thumbnail_1);             
    view.setTag(viewHolder);    
}else{
 viewHolder =  view.getTag(viewHolder); 
}

You should declare the ImageLoader and DisplayImageOptions instance only once. Probably in constructor.

ImageLoader imageLoader = ImageLoader.getInstance();
DisplayImageOptions options = new DisplayImageOptions.Builder().cacheInMemory(true)
        .cacheOnDisc(true).resetViewBeforeLoading(true)
        .showImageForEmptyUri(fallbackImage)
        .showImageOnFail(fallbackImage)
        .showImageOnLoading(fallbackImage).build();
Sangsanger answered 20/1, 2016 at 12:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.