0

Solved: I am using a custom recyclerview adapter with checkbox so that user can select multiple checked items.

In the beginning I faced duplicate checkbox selection while scrolling down so I added a position array to keep checkbox selection true or false.Now duplicate checkbox selection problem is gone but while scrolling down selected checkbox are deselected.My recyclerview adpater is given below,

class IngredientsAdapter(var context: Context?,var activity: Activity, var ingredientList:ArrayList<String>, var imageID:Int):
        RecyclerView.Adapter<IngredientsAdapter.IngredientViewHolder>() {
    private var positionArray:ArrayList<Boolean> = ArrayList(ingredientList.size)
    private val selectedList=ArrayList<String>()
    init {
        for (i in ingredientList.indices) {
            positionArray.add(false)
        }
    }

    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): IngredientViewHolder {
        val layoutInflater = LayoutInflater.from(p0.context)
        return (IngredientsAdapter.IngredientViewHolder(layoutInflater.inflate(R.layout.ingredient_list_row,p0,false)))
    }

    override fun getItemCount(): Int {
       return ingredientList.size
    }
    override fun onBindViewHolder(p0: IngredientViewHolder, p1: Int) {


        p0.imageView.setImageResource(imageID)
        p0.textView.text = ingredientList[p1]
        //added for solution here
        p0.checkBox.setOnCheckedChangeListener(null)

        p0.checkBox.isChecked = positionArray[p1]
        val sharedPreferences= SharedPreferenceHelper(context, SystemConstants.SHARED_PREFS_CHECKDATA)
        val checked=sharedPreferences.getPrefsBooleanValue(ingredientList[p1])
        p0.checkBox.isChecked = checked
        p0.checkBox.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
            if(isChecked){
                positionArray[p1] = true
                selectedList.add(ingredientList[p1])
                ingredientList.get(p1)
                sharedPreferences.addPrefsBooleanVal(ingredientList[p1],true)
                Log.d("debug","selecteditem==>$selectedList")
            }else{
                positionArray[p1] = false
                selectedList.remove(ingredientList[p1])
                sharedPreferences.addPrefsBooleanVal(ingredientList[p1],false)
                Log.d("debug","selecteditem==>$selectedList")
            }
        })
    }

    class IngredientViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var textView= itemView.txt_row!!
        var imageView= itemView.image_view!!
        var checkBox= itemView.chk_row!!
    }
}

Any suggestion is appreciated.

Padmini S
  • 81
  • 9
  • 1
    use onclicklistener – Bek Oct 31 '19 at 05:54
  • @Bek Sometimes back I used listview with java with setCheckedChangedListener and I had no problems, I think I am missing something related to recyclerview here.Thanks for the suggestion , if nothing works I'll use onclicklistener. – Padmini S Oct 31 '19 at 06:09
  • do you know java? – kam1234 Oct 31 '19 at 06:11
  • yes I was working on java before, now slowly changing to kotlin.But that is not solution to my problem. – Padmini S Oct 31 '19 at 06:15
  • i know the solution of this but its in java – kam1234 Oct 31 '19 at 06:30
  • finally this answer https://stackoverflow.com/a/8549246/11560232 helped me to find the solution i.e adding ````p0.checkBox.setOnCheckedChangeListener(null)```` stopped checkboxes from getting deselected while scrolling down. – Padmini S Nov 04 '19 at 06:54

4 Answers4

1

What @Bek suggested is the correct solution. You will need to use OnClickListener. Also what you commented is not wrong either, just what you used to use(ListView) is or was!

Let me break it down for you:

  1. ListView: The older version of RecyclerView.

    Wonder why the developer created another redundant component? Basically Memory Issue! ListView creates as many ItemViews as it requires whereas RecyclerView just recycles the itemView.
    For example if for a list of 100 items if we use ListView it will initially create as many ItemViews as the phone can display. Then as we scroll down it keeps creating more Views, till the 100th item(100 ItemViews in memory). But RecyclerView does this more efficiently it creates as many views it can show + 1 (Next view in list) then it keeps recycling them so we always have only As many views in screen +1 and not 100 when we reach the bottom of the list. For more details read this and this.

  2. OnCheckChangeListener: The Other problem maker!

    This listener is called whenever check changes for the checkbox, Whenever! So if i where to refresh a checkbox this listener(theoretically) will be called! Getting where am going with this? Yup when used along with RecyclerView its gonna cause an havoc in ones code. The moment recyclerView destroys or reuses an ItemView the checkbox is reset which fires the listener and causing your SharedPref to rewrite the check! I tested this by adding logs inside the listener and saw it get triggered for outer most views when it got recycled.

This is just my findings, there maybe some way or fix for this but i too would suggest using OnClickListener and write a listener to change the model in main class rather than sharedPref in adapter.

Oh! You can use ViewHolder.SetIsRecyclable(false), this would stop RecyclerView from Recycling the views and create as many views as there are items in list. But i wouldn't suggest this as the UI as well as UX will be compromised as u might find a bit lag while scrolling(Memory Issue)!

Long Story Short use OnClickListener with RecyclerView!

0

You can use Model class to keep track of each recyclerView item's checkbox. Use setTag and getTag is used to keep track of checkbox status.

Make Model

public class Model {

private boolean isSelected;
private String animal;

public String getAnimal() {
    return animal;
}

public void setAnimal(String animal) {
    this.animal = animal;
}

public boolean getSelected() {
    return isSelected;
}

public void setSelected(boolean selected) {
    isSelected = selected;
}
}

create integer.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="btnplusview">1</integer>
<integer name="btnpluspos">2</integer>
</resources>

Finally adapter looks like this:

 import android.content.Context;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CheckBox;
 import android.widget.TextView;
 import android.widget.Toast;
 import java.util.ArrayList;


public class CustomAdapter extends 
RecyclerView.Adapter<CustomAdapter.MyViewHolder> {

private LayoutInflater inflater;
public static ArrayList<Model> imageModelArrayList;
private Context ctx;

public CustomAdapter(Context ctx, ArrayList<Model> imageModelArrayList) {
inflater = LayoutInflater.from(ctx);
this.imageModelArrayList = imageModelArrayList;
this.ctx = ctx;
}

@Override
public CustomAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int 
viewType) {

View view = inflater.inflate(R.layout.rv_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);

return holder;
}

@Override
public void onBindViewHolder(final CustomAdapter.MyViewHolder holder, int 
position) {
holder.checkBox.setText("Checkbox " + position);
holder.checkBox.setChecked(imageModelArrayList.get(position).getSelected());
holder.tvAnimal.setText(imageModelArrayList.get(position).getAnimal());

// holder.checkBox.setTag(R.integer.btnplusview, convertView);
holder.checkBox.setTag(position);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

        Integer pos = (Integer) holder.checkBox.getTag();
        Toast.makeText(ctx, imageModelArrayList.get(pos).getAnimal() + " 
clicked!", Toast.LENGTH_SHORT).show();

        if (imageModelArrayList.get(pos).getSelected()) {
            imageModelArrayList.get(pos).setSelected(false);
        } else {
            imageModelArrayList.get(pos).setSelected(true);
        }
  }
});


}

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

class MyViewHolder extends RecyclerView.ViewHolder {

protected CheckBox checkBox;
private TextView tvAnimal;

public MyViewHolder(View itemView) {
    super(itemView);

    checkBox = (CheckBox) itemView.findViewById(R.id.cb);
    tvAnimal = (TextView) itemView.findViewById(R.id.animal);
    }

}
}
Tushar Pingale
  • 237
  • 3
  • 18
  • Why? it is almost a perfect answer, what is your problem? The logic for java and koltin is the same, you would not find every single thing written in kotlin, simply because it is already solved in java. – barotia Oct 31 '19 at 06:31
  • Then give a reference to java answer in stackoverflow or some other website, don't paste code here.I know how to convert the logic and that's what I am trying to do . – Padmini S Oct 31 '19 at 06:49
0

OnClickListener won't work when sliding the Switch.

Since RecyclerView is recycling views, a previously attached OnCheckedChangeListener can be triggered when setting checked value for the Switch of the new item.

When binding new data to an item:

   switch.setOnCheckedChangeListener(null) // remove any existing listener from recycled view
   switch.isChecked = [true/false] // will no longer trigger any callback to listener
   switch.setOnCheckedChangeListener { btnView, isChecked ->
       // do exiting stuff
   }
jayeffkay
  • 873
  • 1
  • 13
  • 17
-1

When you call setOnCheckedChangeListener, checkbox has a listener, then recyclerview will multiplex view when you scroll. and you apply p0.checkBox.isChecked = positionArray[p1] , actually you change last poisiton.

For example , you check the first checkbox, arrray[0] is true, then you scroll the view , the next item may the fourth or fifth will have the same checkbox refrence.


Then you call p0.checkBox.isChecked = positionArray[p1] again, but the listener belong the first item . then the listener will change array[0].

So you should call p0.checkBox.setOnCheckedChangeListener before p0.checkBox.isChecked = positionArray[p1]

林世杰
  • 39
  • 2