2

I've looked here and here but I haven't been able to adapt these solutions to fix my problem. Can you help?

I check checkboxes, scroll down in my recyclerview, when I scroll back up, checkboxes are unchecked, or else different ones are checked.

I get why it's happening - as stated here: you should add a boolean variable into your onBindViewHolder to keep your item's selection status, which I believe I am doing with:

theContactsList.get(pos).setSelected(isChecked);

but still checkbox state is not preserved.

Here's my code:

  @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
        //bind the views into the ViewHolder
        //selectPhoneContact is an instance of the SelectPhoneContact class.
        //We will assign each row of the recyclerview to contain details of selectPhoneContact:

        //The number of rows will match the number of phone contacts
        final SelectPhoneContact selectPhoneContact = theContactsList.get(position);

        //if the row is a matching contact
        if (viewHolder.getItemViewType() == 1)

        {
            //in the title textbox in the row, put the corresponding name etc...
            ((MatchingContact) viewHolder).title.setText(selectPhoneContact.getName());
            ((MatchingContact) viewHolder).phone.setText(selectPhoneContact.getPhone());
            //((MatchingContact) viewHolder).check.setText("Cheeckbox" + position);
            ((MatchingContact) viewHolder).check.setChecked(theContactsList.get(position).isSelected);
            ((MatchingContact) viewHolder).check.setTag(position);

          //  ((MatchingContact) viewHolder).check.setChecked(selectPhoneContact.isSelected());



            ((MatchingContact) viewHolder).check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
           // ((MatchingContact) viewHolder).check.setOnClickListener(new CompoundButton.OnClickListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                    //pos is the row number that the clicked checkbox exists in
                    Integer pos = (Integer) ((MatchingContact) viewHolder).check.getTag();

                    //set the value of the checkbox accordingly onCheckedChange,
                    //to true or false
                    theContactsList.get(pos).setSelected(isChecked);

                    if(isChecked == true ) {
                       // ((MatchingContact) viewHolder).check.setChecked(true);
                        Toast.makeText(context_type, theContactsList.get(pos).getPhone() + " clicked!", Toast.LENGTH_SHORT).show();

                    }

                    else {
                       // ((MatchingContact) viewHolder).check.setChecked(false);
                        Toast.makeText(context_type, theContactsList.get(pos).getPhone() + " unclicked!", Toast.LENGTH_SHORT).show();

                    }

                }
            });

            }

    }
CHarris
  • 2,693
  • 8
  • 45
  • 71
  • 1
    IMO, it would be best if save the checked state to your model class or in a list. – Atiq Jan 21 '18 at 21:35
  • @Max Would you suggest any posts/tutorials? – CHarris Jan 21 '18 at 21:43
  • 1
    Your implementation seems correct. You are keeping checkbox state saved in a list. Can you try one thing? Put this line at the top of `onBindView` after you check for the itemViewType, `(MatchingContact) viewHolder).check.setOnCheckedChangeListener(null)` – Napster Jan 21 '18 at 22:10
  • @Napster Tried that, it didn't fix it. I am implementing Ugurcan's suggestion below, will keep you posted. I will be keeping the line of code you say above, as I do believe it is more helpful than harmful - from what I have read. – CHarris Jan 21 '18 at 22:22
  • @max is right! You should save the check state in your model. Also you can use this library, see ViewStates https://github.com/vivchar/RendererRecyclerViewAdapter – Vitaly Jan 26 '18 at 14:42

4 Answers4

1

Have a HashMap<String, Boolean> instance inside your recycler adapter. Keys should be something that uniquely defines your each item, and values indicate whether item is checked or not. Whenever you click a checkbox, call map.put(item.getId(), !map.get(item.getId()) (don't forget to check initial not-exist case). And finally inside onBindViewHolder() method, always set your item's checkbox according to the state hold in HashMap.

Ugurcan Yildirim
  • 5,973
  • 3
  • 42
  • 73
0

Culprit is your OnCheckedChangeListener - keep in mind it fires off even when you change check state programatically.

This is what's currently happening with your implementation:

  1. You bind ViewHolder 1 to position 0
  2. You scroll down, ViewHolder 1 is removed
  3. ViewHolder 1 is reused and bound to position 5 (example)

Then this is executed:

((MatchingContact) viewHolder).check.setChecked(theContactsList.get(5).isSelected);
((MatchingContact) viewHolder).check.setTag(5);

But checkBox already has an OnCheckedChangeListener so it's executed as soon as setChecked() is called and this happens:

// 0 is the tag you assigned to a view, but we're binding item 5 now
theContactsList.get(0).setSelected(isChecked);

You effectively set your check state of position 0 to state of position 5 (which will most likely by false).

You're also creating a ton of OnCheckedChangeListener objects when scrolling through the list, you can create only one in ViewHolders constructor and it will work just as fine since it fetches current position when called.

For solution, simply move your setTag() higher (before setChecked()), but honestly you should use getAdapterPosition() in your OnCheckedChangeListener instead of a tag work around.

Pawel
  • 15,548
  • 3
  • 36
  • 36
0

simply write this one line before calling your adapter.

recyclerView.setItemViewCacheSize(100000);
  • 1
    Thank you for your contribution! Your answer could be even better if you explained what the problem was and how the code you propose here resolves the problem. Also, don't forget to use the appropriate formatting for pieces of code and quotes. This makes questions and answers easier to read and more likely to get upvotes. – Patrick Mar 08 '22 at 06:04
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Patrick Mar 08 '22 at 06:04
-1

Try to use viewHolder.setIsRecyclable(false); at the top of your code :

 @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
    viewHolder.setIsRecyclable(false);

    //other stuff
}
R G
  • 461
  • 3
  • 15