1

I know that this question has been asked several times, but the problem is still present. So the thing is that when i scroll my vertical RecyclerView list items inside ViewHolder are getting shuffled. I have list of some objects with boolean inside it deciding the ViewType, so i can inflate various views. So here is the code:

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

    if(viewType == TYPE_ACTIVE) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_poll_item_active, parent, false);
        return new CardViewHolderActive(view);

    } else if(viewType == TYPE_INACTIVE) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_poll_item_past, parent, false);
        return new CardViewHolderPast(view);
    }

    throw new RuntimeException("There is no type that matches the type " + viewType + " + make sure your using types correctly");

}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    Poll poll = mListPollObjects.get(position);
    Log.d("ADAPTER", "position - " + position);
    if(holder instanceof CardViewHolderActive) {
        ((CardViewHolderActive) holder).bindActivePolls(poll);

    } else if(holder instanceof CardViewHolderPast) {

        if(position == 0) {
            ((CardViewHolderPast) holder).bindPastPolls(poll, true);

        } else if(getItemViewType(position - 1) == TYPE_INACTIVE) {
            ((CardViewHolderPast) holder).bindPastPolls(poll, false);

        } else if(getItemViewType(position - 1) == TYPE_ACTIVE)
            ((CardViewHolderPast) holder).bindPastPolls(poll, true);
    }

}

@Override
public int getItemCount() {
    return mListPollObjects.size();
}


/**
 * Added to solve the RecyclerView shuffling problem.
 *
 * @param position
 * @return
 */
@Override
public long getItemId(int position) {
    return position;
}

/**
 * Added to solve the RecyclerView shuffling problem.
 *
 * @param position
 * @return
 */

@Override
public int getItemViewType(int position) {
    Poll poll = mListPollObjects.get(position);

    if(poll.getStatus().equals(Poll.STATUS_ACTIVE)) {
        return TYPE_ACTIVE;
    } else {
        return TYPE_INACTIVE;
    }
}

So when i had:

@Override
public int getItemViewType(int position) {
    return position;
}

the shuffling was gone, however i had to do binding stuff in onBindViewHolder all manually, so i decided to switch to the viewTypes. I really can't figure it out what is going on here, and it's pretty annoying... Hope i provided enough informations. Thanks in advance!

Here is the bindActivePolls method from CardViewHolderActive:

public void bindActivePolls(final Poll polls) {

        int listImageSize = polls.getListImageObjects().size();

        //tvPollTitle.setText(polls.getStatus() + " - " + polls.getId());

        if(polls.getTitle() != null || !polls.getTitle().equals("")) {
            tvPollTitle.setText(polls.getTitle());
        } else {
            tvPollTitle.setText("");
        }

        tvCurrentVotes.setText(String.valueOf(polls.getVotesCount()) + "/");
        tvUpToVotes.setText(String.valueOf(polls.getMaxVotes()));

        // Setting the percentage bars.

        pbProgressOne.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(0).getVotes()));
        tvProgressOne.setText(String.valueOf(pbProgressOne.getProgress()) + mContext.getString(R.string.percentage));

        pbProgressTwo.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(1).getVotes()));
        tvProgressTwo.setText(String.valueOf(pbProgressTwo.getProgress()) + mContext.getString(R.string.percentage));

        GlideApp.with(mContext)
                .asDrawable()
                .load(polls.getListImageObjects().get(0).getUrlImageMainThumbnail())
                .transforms(new CenterCrop(), new RoundedCorners(20))
                .into(ivImageOne);

        GlideApp.with(mContext)
                .asDrawable()
                .load(polls.getListImageObjects().get(1).getUrlImageMainThumbnail())
                .transforms(new CenterCrop(), new RoundedCorners(20))
                .into(ivImageTwo);

        if(listImageSize >= 3) {

            pbProgressThree.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(2).getVotes()));
            tvProgressThree.setText(String.valueOf(pbProgressThree.getProgress()) + mContext.getString(R.string.percentage));

            GlideApp.with(mContext)
                    .asDrawable()
                    .load(polls.getListImageObjects().get(2).getUrlImageMainThumbnail())
                    .transforms(new CenterCrop(), new RoundedCorners(20))
                    .into(ivImageThree);
        }
        if(listImageSize >= 4) {

            pbProgressFour.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(3).getVotes()));
            tvProgressFour.setText(String.valueOf(pbProgressFour.getProgress()) + mContext.getString(R.string.percentage));

            GlideApp.with(mContext)
                    .asDrawable()
                    .load(polls.getListImageObjects().get(3).getUrlImageMainThumbnail())
                    .transforms(new CenterCrop(), new RoundedCorners(20))
                    .into(ivImageFour);
        }


    }

And bindPastPolls() from CardViewHolderPast:

public void bindPastPolls(Poll polls, boolean isFirstPast) {


        if(isFirstPast) {
            vSeparator.setVisibility(View.VISIBLE);
            tvPastContestSeparator.setVisibility(View.VISIBLE);
        }

        int listImageSize = polls.getListImageObjects().size();
        //tvPollTitle.setText(polls.getStatus() + " - " + polls.getId());
        if(polls.getTitle() != null || !polls.getTitle().equals("")) {
            tvPollTitle.setText(polls.getTitle());
        } else {
            tvPollTitle.setText("");
        }
        tvCurrentVotes.setText(String.valueOf(polls.getVotesCount()) + "/");
        tvUpToVotes.setText(String.valueOf(polls.getMaxVotes()));

        pbProgressOne.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(0).getVotes()));
        tvProgressOne.setText(String.valueOf(pbProgressOne.getProgress()) + mContext.getString(R.string.percentage));

        pbProgressTwo.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(1).getVotes()));
        tvProgressTwo.setText(String.valueOf(pbProgressTwo.getProgress()) + mContext.getString(R.string.percentage));



        GlideApp.with(mContext)
                .asDrawable()
                .load(polls.getListImageObjects().get(0).getUrlImageMainThumbnail())
                .transforms(new CenterCrop(), new RoundedCorners(20))
                .into(ivImageOne);

        GlideApp.with(mContext)
                .asDrawable()
                .load(polls.getListImageObjects().get(1).getUrlImageMainThumbnail())
                .transforms(new CenterCrop(), new RoundedCorners(20))
                .into(ivImageTwo);

        if(listImageSize >= 3) {

            pbProgressThree.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(2).getVotes()));
            tvProgressThree.setText(String.valueOf(pbProgressThree.getProgress()) + mContext.getString(R.string.percentage));

            GlideApp.with(mContext)
                    .asDrawable()
                    .load(polls.getListImageObjects().get(2).getUrlImageMainThumbnail())
                    .transforms(new CenterCrop(), new RoundedCorners(20))
                    .into(ivImageThree);
        }
        if(listImageSize >= 4) {

            pbProgressFour.setProgress(votePercentageCalculator(polls.getVotesCount(), polls.getListImageObjects().get(3).getVotes()));
            tvProgressFour.setText(String.valueOf(pbProgressFour.getProgress()) + mContext.getString(R.string.percentage));

            GlideApp.with(mContext)
                    .asDrawable()
                    .load(polls.getListImageObjects().get(3).getUrlImageMainThumbnail())
                    .transforms(new CenterCrop(), new RoundedCorners(20))
                    .into(ivImageFour);
        }

    }

Update: when i set holder.setIsRecyclable(false) it works properly, but i guess it is losing its main purpose of RecyclerView?

RadoVidjen
  • 432
  • 7
  • 17
  • Possible duplicate of [Recyclerview on Scrolling values changing from adapter](https://stackoverflow.com/questions/35200540/recyclerview-on-scrolling-values-changing-from-adapter) –  Jul 17 '18 at 09:47
  • Its not duplicate if you read it carefully, but it looks similar. – RadoVidjen Jul 17 '18 at 09:53
  • Post code for `bindActivePolls(poll)` and `bindPastPolls(poll, true);` – prashant17 Jul 17 '18 at 09:56
  • that bindVIewHolder looks weird... why are you checking viewtype of previous item? You should only rely on `holder.getItemViewType()` there. – Pawel Jul 17 '18 at 10:01
  • Question has been updated. – RadoVidjen Jul 17 '18 at 10:03
  • @Pawel I am checking because i need to place some sort of separator when i run out of active objects.. So active objects comes first, then past objects. I have two layouts which are very similar, but for past objects i have some separator, and i want it to be seen only if the past objects is frist in the list (no active at the moment) or when its first after active. – RadoVidjen Jul 17 '18 at 10:05
  • Your `bindPastPolls` has condition for `isFirstPost` but there's no `else` branch that hides the separator so it lingers after recycling. – Pawel Jul 17 '18 at 10:12
  • @Pawel Thansk for pointing that out, it fixed other problem with weird separator palcing after some scrolling.. However, without holder.setIsRecyclable(false) images are still getting shuffled or added to the wrong places after scrolling which is the main problem, and i`m sure i`m not the only one facing it... Cant figure it out is this a bug on RecyclerView or i`m simply doing something wrong.. – RadoVidjen Jul 17 '18 at 10:19
  • I don't use Glide, but it's probably asynchronous nature of it. See if you can store the async request within viewholder and force cancel it onBind to prevent loading wrong images. – Pawel Jul 17 '18 at 10:51
  • Are you using RecyclerView.Adapter.setHasStableIds(true) ? – Nick Mowen Jul 17 '18 at 12:37
  • @NickMowen Yes i am, i have tried everything, with various viewTypes RecyclerView simply does not work... it is so anoying.. – RadoVidjen Jul 17 '18 at 12:55
  • @RadoVidjen using stableIds with the position, as explained in the docs, is incorrect and the cause of many odd behaviors. Stable IDs are meant to be the ID of an item irrespective of it's position, so the recyclerview can know which items have moved and which ones haven't. I'd either get rid of stable IDs or use something of the Poll that is unique and stable for the Id – Nick Mowen Jul 17 '18 at 13:16
  • @NickMowen Thank you Nick for the answer, i will try without it.. I was just practicing the most common solution for that shuffle thing, where was suggested to override getItemId(int position) to overpass the mentioned shuffle problem... so confused... gonna keep trying... – RadoVidjen Jul 17 '18 at 13:39
  • Well i guess that removing `getItemId()` method fixed the problem... – RadoVidjen Jul 18 '18 at 08:00
  • And it was working because i had holder.setIsRecyclable(false)... when i remove that, items in the list item are still getting shuffled... i'm so devastated... – RadoVidjen Jul 18 '18 at 13:22
  • @RadoVidjen Hey, did you find the solution to this problem? Even i am going through the same now – Yogesh Telang Nov 26 '18 at 09:30
  • @YogeshTelang As i remember, i have added the `holder.setIsRecyclable(true)` in the `onBindViewHolder()`. Also, try to implement `onViewRecycled()`, clearing the currently unused views. Hope i helped you a bit :) – RadoVidjen Nov 27 '18 at 13:58
  • @RadoVidjen Thanks for your response. I have solved it. I did it by clearing and recreating the views inside onBindViewHolder() – Yogesh Telang Nov 28 '18 at 17:10

2 Answers2

2

I had this problem too some years ago and it was because I was using glide if there is a image from the API and if not I was not setting a image. I added else statement to indicate a src for the image view. If you want it blank, set a src which match with the background.

JavierSegoviaCordoba
  • 6,531
  • 9
  • 37
  • 51
1

In Java

@Override
public int getItemViewType(int position)
{
    return position;
}

In Kotlin

override fun getItemViewType(position: Int): Int = position
Razz
  • 452
  • 6
  • 9