DJ-DOO
DJ-DOO

Reputation: 4781

Recyclerview Recycled viewholders merging

I have a recycler view. I have many view holders for different content types.

I create my holders like this:

  /**
     * Create instance of
     * compatible viewholder
     *
     * @param viewType
     * @param parent
     * @return
     */
    private AbstractHolder createAbstractHolder(int viewType, ViewGroup parent) {
        AbstractHolder holder = null;

        switch (viewType) {
            case VersyConstants.HOLDER_TYPE_1:
                holder = ViewHolder_Var1.create(parent, mUserHomeFragment, mUserStreamFragment);

                break;

            case VersyConstants.HOLDER_TYPE_2:
                holder = ViewHolder_Var2.create(parent, mUserHomeFragment, mUserStreamFragment);
                break;

            case VersyConstants.HOLDER_TYPE_3:
                holder = ViewHolder_Var3.create(parent, mUserHomeFragment, mUserStreamFragment);
                L.i(getClass().getSimpleName(), "HOLDER 3");
                break;

            case VersyConstants.HOLDER_TYPE_4:
                holder = ViewHolder_Var4.create(parent, mUserHomeFragment, mUserStreamFragment);
                L.i(getClass().getSimpleName(), "HOLDER 4");
                break;

            case VersyConstants.HOLDER_TYPE_5:
                holder = ViewHolder_Var5.create(parent, mUserHomeFragment, mUserStreamFragment);
                L.i(getClass().getSimpleName(), "HOLDER 5");
                break;

            case VersyConstants.HOLDER_TYPE_6:
                holder = ViewHolder_Var6.create(parent,  mUserHomeFragment, mUserStreamFragment);
                L.i(getClass().getSimpleName(), "HOLDER 6");
                break;

            case VersyConstants.HOLDER_TYPE_7:
                holder = ViewHolder_Var7.create(parent, mUserHomeFragment, mUserStreamFragment);
                L.i(getClass().getSimpleName(), "HOLDER 7");
                break;

            case VersyConstants.HOLDER_TYPE_8:
                holder = ViewHolder_Var8.create(parent, mUserHomeFragment, mUserStreamFragment);
                L.i(getClass().getSimpleName(), "HOLDER 8");
                break;

            case VersyConstants.HOLDER_TYPE_9:
                holder = ViewHolder_Var9.create(parent, mUserHomeFragment, mUserStreamFragment);
                break;

            case VersyConstants.HOLDER_TYPE_10:
                holder = ViewHolder_Var10.create(parent, mThumbnailViewToLoaderMap, mUserHomeFragment, mUserStreamFragment);
        }
        return holder;
    }

The issue is I have when I scroll my recycler view down, and for example view holder 3 is created for the 1st item and the 5th item, when I scroll back up to item no. 1, it has recycled view holder from 5th item, which I know is the idea of the recycler view.

In the viewholders I am hiding/showing textviews if the relevant data comes in JSON. I am displaying hash tags if present, so in my first item below:Recyclerview item 1 - no tags

Then when I scroll down to the 5th item which has tags (below):Recyclerview 5th item with tags

Then when I scroll back up to the first item the tags are added when they shouldn't be as there's no tag data associated with this object (see below):

First item  now with tags

I have debugged and there isn't a mix up in object data, there are no tags added via code, this is down to recyclerview recycling, both of these items have the same view holder (view holder 3). Is there anyway to prevent this?

Below is the method to populate the tags. Depending on list size I set visibility of text view to visible and populate:

 protected void populateTags(List<String>tags, TextView[] array){
        for(int i=0;i<tags.size(); i++){
            array[i].setText(tags.get(i));
            array[i].setVisibility(View.VISIBLE);
        }
    }

I call the above like this List<String> tagsList = feedContent.getTags(); if(tagsList.size>0)populateTags(tagsList, tagsArray); This doesn't just happen with item 1 & 5, it depends on which views are being reused. If there is a view reused for a view already displaying tags, the tags from the reused view are added. Its very bad

Upvotes: 0

Views: 1440

Answers (2)

Kelevandos
Kelevandos

Reputation: 7082

Basically, RecyclerView is, well, recycling. It reuses the containers, but does not reset them!

It means that each time you inflate a ViewHolder with data, you need to both add the new data and remove the old data (or hide some Views). If you don't, you will end up with the data from previously inflated object :-(

EDIT: So, if you have something like this in your inflating code

if(item.getTagsCount() > 0)
    tags.setVisibility(View.VISIBLE);

you need to update it to something like this

if(item.getTagsCount() > 0)
    tags.setVisibility(View.VISIBLE);
else 
    tags.setVisibility(View.GONE);

or

if(item.getTagsCount() > 0)
    tags.setVisibility(View.VISIBLE);
else if(item.getTagsCount() == 0)
    tags.setVisibility(View.GONE);

Of course, this is just an exemplary condition. Just remember that it you put some data into the ViewHolder, you also need to safeguard against the case where there is no data to put there.

Upvotes: 3

DJ-DOO
DJ-DOO

Reputation: 4781

So I found this question RecyclerView adapter taking wrong values on SO. Same issue as the above but suggested answer wasn't working. I noticed "This usually happens when you have something like "if (field != null) holder.setField(field)", without an else. The holder is recycled, this means that it will have values there, so you need to clean or replace EVERY value, if it's null you should nullit, if it's not, you should write it, ALWAYS. It's late, but, as an answer for others." in the comments. So I changed my call to populate from if(tagsList.size>0)populateTags(tagsList, tagsArray); to populateTags(tagsList, tagsArray); as I didn't have an else and that logic was in the method.

So that's it..hopefully it'll be of some help to someone else

Upvotes: 0

Related Questions