'In Recycleview , Why current row keeping previous Model class name, even after refreshing row ? See code

I have a RecyclerView, In it i have some rows showing (Text View 1) Product name , Button and a Text View 2 , This Text View 2 is just to test the problem, When i press Button in several rows to add it works fine But the issue is when i press remove after remove in several rows, Its updating Text View 2 with previous row's Product Name, So i don't know why its keeping previous rows name saved somewhere while i refreshed the row by notifyItemChanged(position). Here is a test version of my Project showing the issue, Can anyone exactly say why its happening and how to solve it, Run the project to see whats happening exactly. This is my RecyclerView.

public class Recyclerview extends RecyclerView.Adapter<Recyclerview.MyViewHolder> {
    Context context;
    ArrayList<Product> list;

    public Recyclerview(Context context, ArrayList<Product> list) {
        this.context = context;
        this.list = list;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.row_product, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Product product = list.get(position);

        holder.xName.setText(product.getName());

        SharedPreferences sPrefs = context.getSharedPreferences("Item details", Context.MODE_PRIVATE);

        if (sPrefs.contains(product.getName())) {
            holder.test.setText(product.getName());

            holder.addButton.setText("Remove");
        } else {
            holder.addButton.setText("Add");
        }
        holder.addButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                if (holder.addButton.getText().toString().equals("Add")) {

                    HashMap<String, Object> hashMap = new HashMap<>();
                    hashMap.put("Item Name", product.getName());
                    saveMap(product.getName(), hashMap);

                    notifyItemChanged(holder.getLayoutPosition());

                } else if (holder.addButton.getText().toString().equals("Remove")) {

                    SharedPreferences mPrefs = context.getSharedPreferences("Item details", Context.MODE_PRIVATE);
                    SharedPreferences.Editor prefsEditor = mPrefs.edit()
                            .remove(product.getName());
                    prefsEditor.apply();

                    notifyItemChanged(holder.getLayoutPosition());
                }
            }

            private void saveMap(String key, Object obj) {
                SharedPreferences prefs = context.getSharedPreferences("Item details", Context.MODE_PRIVATE);
                SharedPreferences.Editor editor = prefs.edit();
                Gson gson = new Gson();
                String json = gson.toJson(obj);
                editor.putString(key, json);
                editor.apply();
            }
        });
    }

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

    public static class MyViewHolder extends RecyclerView.ViewHolder {

        Button addButton;
        TextView xName, test;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            xName = itemView.findViewById(R.id.product_name);
            addButton = itemView.findViewById(R.id.btnAddItem);
            test = itemView.findViewById(R.id.testUid);
        }
    }
}

This is my Model class

public class Product {

    Context mContext;
    String name;

    public Product(Context mContext, String name) {

        this.mContext = mContext;
        this.name = name;

    }

    public Context getmContext() {
        return mContext;
    }

    public void setmContext(Context mContext) {
        this.mContext = mContext;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

This is my Main Activity.

public class MainActivity extends AppCompatActivity {
    RecyclerView recview;
    Recyclerview productAdapter;
    ArrayList<Product> productList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recview = findViewById(R.id.recyclerview);
        ViewCompat.setNestedScrollingEnabled(recview, false);
        recview.setHasFixedSize(true);
        recview.setLayoutManager(new LinearLayoutManager(this));
        productList = new ArrayList<>();

        productList.add(new Product(this,"Sugar"));
        productList.add(new Product(this,"Coffee"));
        productList.add(new Product(this,"Tea"));
        productList.add(new Product(this,"Milk"));
        productList.add(new Product(this,"Vegetable"));
        productList.add(new Product(this,"Bread"));
        productList.add(new Product(this,"Jam"));
        productList.add(new Product(this,"Butter"));
        productList.add(new Product(this,"Sandwich"));
        productList.add(new Product(this,"Roll"));
        productList.add(new Product(this,"Salt"));
        productAdapter = new Recyclerview(this, productList);
        recview.setAdapter(productAdapter);
    }
}

This is activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#9C9696"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:listitem="@layout/row_product"/>

</androidx.constraintlayout.widget.ConstraintLayout>

This is my row_product.xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:layout_margin="5dp"
    android:background="#FFFFFF"
    xmlns:app="http://schemas.android.com/apk/res-auto">

        <TextView
            android:gravity="center"
            android:textSize="15sp"
            android:text="Nothing"
            android:id="@+id/testUid"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:textColor="#A10000"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:id="@+id/product_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textStyle="bold"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/testUid"
            app:layout_wrapBehaviorInParent="horizontal_only" />

    <Button
        android:id="@+id/btnAddItem"
        android:layout_width="200dp"
        android:layout_height="40dp"
        android:layout_marginTop="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/testUid" />

</androidx.constraintlayout.widget.ConstraintLayout>


Solution 1:[1]

the problem is that you MUST set every View in onBindViewHolder and you are not doing this in below snippet

    if (sPrefs.contains(product.getName())) {
        holder.test.setText(product.getName());

        holder.addButton.setText("Remove");
    } else {
        holder.addButton.setText("Add");
    }

holder.test don't have any text set in else, so it will stay same as previously set if this is recycled item. you should probably clean this line then

    } else {
        holder.test.setText("");
        holder.addButton.setText("Add");
    }

get familiar with "recycling pattern", how it works any why... HERE you have an example for old-fashioned ListView. not every developer was using recycling pattern in adapter, but it's crucial for performance, thus for this reason (and probably some other) Google decided to make new widget called RecyclerView, which has recycling pattern "built-in", even got name from it

PS. don't keep Context in model, never

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1