RecyclerView messed up data when scrolling
Asked Answered
T

11

22

Having a problem when scrolling RecyclerView after scrolling down and up. The idea is to change elements color, but when I scroll down everything is great and when the scroll goes up - the elements, which are shouldn't be colored are changing color.

Here's my adapter:

public class NotificationsAdapter extends RecyclerView.Adapter<NotificationsAdapter.ViewHolder> {

private NotificationData notificationData;
private Context mContext;
private ArrayList<NotificationData> infromationList = new ArrayList<>();


public NotificationsAdapter(Context context, ArrayList<NotificationData> infromationList) {
    this.infromationList = infromationList;
    this.mContext = context;
}


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

    View itemLayoutView;
    ViewHolder viewHolder;

    itemLayoutView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.notification_single_item, parent, false);
    viewHolder = new ViewHolder(itemLayoutView, viewType);

    return viewHolder;
}

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

    notificationData = infromationList.get(position);
    holder.notificationDate.setText(convertDate(notificationData.getDate()));
    holder.notificationStatus.setText(notificationData.getNotificationStatus());
    holder.orderDescription.setText(notificationData.getNotificationLabel());

    if ("true".equals(notificationData.getReadStatus())) {
        holder.root.setBackgroundColor(mContext.getResources().getColor(R.color.white));
        holder.notificationStatus.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
    }

}

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

public static class ViewHolder extends RecyclerView.ViewHolder {

    public TextView notificationDate;
    public TextView notificationStatus;
    public TextView orderDescription;
    public LinearLayout root;

    public ViewHolder(View itemView, int position) {
        super(itemView);

        notificationDate = (TextView) itemView.findViewById(R.id.notificationDate);
        notificationStatus = (TextView) itemView.findViewById(R.id.notificationStatus);
        orderDescription = (TextView) itemView.findViewById(R.id.orderDescription);
        root = (LinearLayout) itemView.findViewById(R.id.root);
    }

}

private String convertDate(String date) {
    String convertedDate;

    String[] parts = new String[2];
    parts = date.split("T");
    date = parts[0];

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
    Date testDate = null;
    try {
        testDate = sdf.parse(date);
    }catch(Exception ex){
        ex.printStackTrace();
    }
    SimpleDateFormat formatter = new SimpleDateFormat("dd.mm.yyyy");
    convertedDate = formatter.format(testDate);

    return convertedDate;
}
}
Typewriting answered 6/5, 2015 at 16:6 Comment(1)
#29702857Southland
B
49

I had the same problem and the only solution I found for this is:

holder.setIsRecyclable(false);

Your recycler will not recycle anymore so the items will be the same when you scroll, and if you want to delete some item do not use notifyitemRemoved(position), use notifyDataSetChanged() instead.

Bechuanaland answered 20/11, 2015 at 21:18 Comment(3)
BEWARE: You should not use either of these advices. As Jhonatan said himself, the views will no longer be recycled, which forfeits the purpose of the recyclerview entirely, and will lead to bad performance. Also using the brute-force notifyDataSetChanged() should always be your last resort - if at all possible, attempt to let the adapter know exactly which items have been updated when removing or adding data - this will not only improve performance, but also let it perform animations appropriately.Crumpled
This worked for me. But after reading @Crumpled comment I decided to check more answers. Senator's answer also worked for me.Hypozeuxis
This worked for me but having performance issues, recycler view seems very laggy. Answer from @Nathan said to add setHasStableIds(true); didn't worked for me. How do i solve this issue for better performance ?Mink
W
40

Add setHasStableIds(true); in your adapter constructor and Override these two methodes in adapter.

@Override
public long getItemId(int position) {
            return position;
}

@Override
public int getItemViewType(int position) {
       return position;
}
Window answered 3/4, 2017 at 6:33 Comment(2)
Can you provide more explanation on this??Weave
I found an explanation hereBergerac
D
6

There is problem in your onBindViewHolder(...), should be:

if ("true".equals(notificationData.getReadStatus())) {
    holder.root.setBackgroundColor(mContext.getResources().getColor(R.color.white));
    holder.notificationStatus.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
}
else {
    holder.root.setBackgroundColor(yourDefaultColor);
    holder.notificationStatus.setTypeface(yourDefaultTypeface);

}
Druci answered 6/5, 2015 at 16:30 Comment(2)
This worked for me. I was thinking that default values are by defauf set, but was in totally missunderstanding.Sculptor
Correct, you should always have else branch inside onBindViewHolder(...).Brakpan
S
4
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
    final UserData userdata = userdataList.get(position);

    holder.setIsRecyclable(false);

    holder.name.setText(userdata.getName());
    holder.active.setChecked(userdata.getActive());

    String userPic = userdata.getPic();


    holder.active.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
            userdata.setActive(isChecked);
        }
    });
}
Suckle answered 4/7, 2019 at 13:36 Comment(0)
K
2

Try adding this in the adapter.

@Override
public int getItemViewType(int position)
{
    return position;
}
Kiel answered 13/3, 2019 at 13:0 Comment(1)
with this u need to add adapter.setHasStableIds(true) and override fun getItemId(position: Int): Long { return list.get(position).index!! } override fun getItemViewType(position: Int): Int { return list.get(position).index!!.toInt() } instead of position return a unique Long and Integer valueMig
O
1

If someone might face issues with some of the fields in the viewholder getting random values, then try to set all the fields with atleast any default value.

Oz answered 9/10, 2019 at 11:58 Comment(0)
S
0
 @Override
public DataObjectHolder onCreateViewHolder(ViewGroup parent,
                                           int viewType) {
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.custom_layout, parent, false);

    DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
    dataObjectHolder.setIsRecyclable(false);

    return dataObjectHolder;
}
Syllabize answered 11/8, 2016 at 6:46 Comment(2)
what is DataObjectHolder ?Centner
ViewHolder ClassSyllabize
I
0

The best way is indicate an ArrayList for example as a Model and have some parameters and define setter and getter for that.

package com.test.mohammaddvi.snappfood.Model;

public class OfferList {
private boolean visibilityOrder;
private int number;

public OfferList(int number, boolean visibilityOrder) {
   this.number=number;
   this.visibilityOrder=visibilityOrder;
}

public boolean isVisibilityOrder() {
    return visibilityOrder;
}

public void setVisibilityOrder(boolean visibilityOrder) {
    this.visibilityOrder = visibilityOrder;
}

public int getNumber() {
    return number;
}

public void setNumber(int number) {
    this.number = number;
}

}

and set the the variables as where you want and for get you must do it in onBindViewHolder of your recyclerview Adapter:

if (offerList.isVisibilityOrder()) {
        holder.foodMinusButton.setVisibility(View.VISIBLE);
        holder.foodOrderNumber.setText(offerList.getNumber() + "");
        holder.foodOrderNumber.setVisibility(View.VISIBLE);
    } else {
        holder.foodMinusButton.setVisibility(View.INVISIBLE);
    }

and indicate it your recyclerview adapter:

public class RecyclerViewMenuFragmentAdapter extends RecyclerView.Adapter<RecyclerViewMenuFragmentAdapter.SingleItemInMenuFragment> {

private ArrayList<Food> foodList;
private Context mContext;
private List<OfferList> offers;

public RecyclerViewMenuFragmentAdapter(ArrayList<Food> foodList, Context mContext, List<OfferList> offers) {
    this.foodList = foodList;
    this.mContext = mContext;
    this.offers = offers;
}
Iphlgenia answered 15/7, 2018 at 6:57 Comment(0)
H
0
class AnyRVAdapter: androidx.recyclerview.widget.RecyclerView.Adapter<AnyRVAdapter.MViewHolder>() {

// put saver outside viewholder
val saveLayId = mutableListOf<Int>()

inner class MViewHolder(itemView: View) :
    androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) {       

    fun bindModel(d: TesListModel.MList, position:Int) {

       // concept here
       val showedId= saveLayId.find { s -> s == layoutPosition}
       if (idClicked == null) {            
           // save the layout id
           lyClicked.visibility = View.VISIBLE
           saveLayId.add(layoutPosition)
       } else {
           // remove the layout id
           lyClicked.visibility = View.INVISIBLE
           saveLayId.remove(layoutPosition)
       }         
    }
}

but i think this code is heavy if you use for large data set.

Hance answered 2/9, 2019 at 4:48 Comment(0)
O
0

Guys this has worked for me..

override fun setHasStableIds(hasStableIds: Boolean) {
    setHasStableIds(true)
}

override fun getItemId(position: Int): Long {
    return position.toLong()
}

override fun getItemViewType(position: Int): Int {
    return position
}
Olivaolivaceous answered 26/8, 2022 at 16:34 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Button
S
0

In you onBindView method, please set this to false

holder.setIsRecyclable(false)

Seminole answered 3/5, 2023 at 2:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.