0

I have a dialog which takes in user input through a list with checkboxes, whose layout is a RecyclerView. But when I select a CheckBox in the list, another CheckBox further down in the list also gets checked, but which I didn't do. These images will help illustrate my point.

Here, I've only selected Calendar and Camera:

enter image description here

but further down in the list, Google and Maps also get selected which I didn't select.

enter image description here

My code for bindActivity is:

public void bindActivity(ResolveInfo resolveInfo)
        {
            mResolveInfo = resolveInfo;
            PackageManager pm = getActivity().getPackageManager();
            String appName = mResolveInfo.loadLabel(pm).toString();
            mAppImageView.setImageDrawable(resolveInfo.loadIcon(pm));
            mAppTextView.setText(appName);
        }

If I add mAppCheckBox.setChecked(false) in bindActivity, then when i go further down in the list and the RecyclerView 'recycles' the list, and then I go up, my earlier selection becomes unselected.

I would love any suggestions on how to get rid of the 'sticky' checkbox.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • Have you tried using `setMultiChoiceItems` on your builder? – Nissim R Jun 18 '17 at 10:17
  • That uses CharSequence and i'm populating the dialog with a ResolveInfo List. I would like to continue using that as that way i can also show icons alongside the app's name –  Jun 18 '17 at 10:21
  • I would also like to know why my question got downvoted. I'm new here and would like to know what i did wrong. –  Jun 19 '17 at 20:40
  • Oh man, i just read what a bounty is. Thank you for doing this senpai. –  Jun 20 '17 at 14:52
  • it is because views are recycled in listview. Read this: https://stackoverflow.com/questions/10895763/checkbox-unchecked-when-i-scroll-listview-in-android and https://stackoverflow.com/questions/5438375/custom-listview-with-checkbox-problem. It will help you understand the issue as well as solution – Ankit Aggarwal Jun 20 '17 at 19:33

2 Answers2

2

You see that behavior, because ViewHolders are being reused. Once you scroll down, the ViewHolder of the upper items (that are not visible now) will be reused for the new items that are going to be displayed. This ViewHolder had a CheckBox that was checked previously and nobody had explicitly said, that it should be unchecked.

You should take care of resetting the state correctly. You can save checked items positions in an array and later in onBindViewHolder() set the selected state of CheckBox appropriately:

public class MyAdapter extends RecyclerView.Adapter<Item> {

  final boolean[] checkedArr;

  public MyAdapter(List<Item> list) {
    checkedArr = new boolean[list.size()];
    // Filling all the items as unchecked by default
    Arrays.fill(checkedArr, false);
  }

  @Override public void onBindViewHolder(Item holder, int position) {
    // You have removed the listener in `onViewRecycled`
    // Thus, this `setChecked()` won't cause any listener to be fired
    holder.checkbox.setChecked(checkedArr[holder.getAdapterPosition()]);
    holder.checkbox.setOnCheckedChangeListener(
        (buttonView, isChecked) -> {
          // Retaining checked state in the array
          checkedArr[holder.getAdapterPosition()] = isChecked;
        });
  }

  @Override public void onViewRecycled(Item holder) {
    super.onViewRecycled(holder);
    // When view is being recycled remove the listener
    holder.checkbox.setOnCheckedChangeListener(null);
  }
  ...
}

Or you can refrain from "reinventing a wheel" and use MultiViewAdapter that is shipping with checkboxes feature: single, single or none, multiple modes.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • It does work, but now how do i get the names of the apps checked? Earlier i used to call `setOnCheckedChangeListener` in my activity holder, and passed the `ResolveInfo` value to another `List` and worked from there. But now the listener does not get triggered for that. –  Jun 24 '17 at 17:59
  • 1
    By looping on `checkedArr`. If `checkedArr[i] == true`, then `tempCollection.add(list.get(i));`. Lastly, when loop finishes, return `tempCollection`, which will contain all checked apps. – azizbekian Jun 24 '17 at 18:09
  • Thanks mate. Also, how would I go about subtracting two lists? Here, I've a list, `mAllApps` which got populated by querying the `PackageManager` and another list 'mBlockedApps` which i get by user input. So how would i go about creating a `mAllowedApps`? –  Jun 25 '17 at 10:49
  • Didn't get it. You may create a new question as well, because as I feel it has no connection with this one. – azizbekian Jun 25 '17 at 11:52
  • 1
    Use `removeAll`? Here's a link to the android reference section: https://developer.android.com/reference/java/util/List.html#removeAll(java.util.Collection>) – Nissim R Jun 25 '17 at 17:03
0

a) It doesn't look like you are setting the checked state of the checkbox when you are binding the view. Keep track of the items that have been selected, and then set the checked state when you doing view binding.

b) I'm not exactly sure what you mean, but from what I gather, the dialog documenation might be what you are looking for when communicating with the parent.

AChez9
  • 158
  • 1
  • 7
  • What i mean by (b) is that i can't take in input through a traditional `for` loop since i don't know how many items the list will have. If an 'enhanced `for`' loop is viable, then how will i use that over here? –  Jun 18 '17 at 22:03
  • Still not totally following. When a checkbox is checked, or in the way you have it when a list item is clicked, at that item to a separate list instance in the adapter. Then when you click your positive button in the dialog, you can take whatever actions you wish on that list (for example passing it back to the activity using a listener pattern as described in the documentation I provided). – AChez9 Jun 18 '17 at 22:09
  • I will update my question after following what you suggested for (a). I suppose then i'll be able to explain you more concretely what my second problem is. –  Jun 19 '17 at 09:22
  • I found solution for (b) but (a) still persists. I've edited the the entire question to reflect my problem. –  Jun 19 '17 at 19:55
  • @SamyakChaudhary, did you try what I said in the answer? Don't just set it to false, keep track of which items have been checked in a list, and then set the check value of the checkbox based on whether the list contains that item id or not. – AChez9 Jun 20 '17 at 15:49
  • The first time the app will run, all the checkboxes have to be set to false as till then there has never been any user input. At subsequent runs, i have written code to track the boxes which were checked in the previous run through `SharedPreferences`. But that doesn't work as even in subsequent runs, the sticky situation persists. And due to this, it becomes completely broken. Right now i'm following the links provided by @AnkitAggarwal, to try and solve the problem. –  Jun 21 '17 at 05:26