简体   繁体   中英

Implementing checkbox in a Recylerview. MVVM Room ViewModel LiveData Recylerview Checkbox

I have implemented a MVVM pattern android java application with Room, ViewModel, LiveData, Recylerview.

  1. The recyclerview has a list of items and there is a checkbox in each item.
  2. When checkbox at the recyclerview item is enabled or disabled, the model is updatedat the Room Database via ViewModel.
  3. To listen the checkbox enable or disable event on each recyclerview item, I have implemented a OnCheckedChangeListener.

It is fine with the recyclerview with 1 or 2 items,

But when the recyclerview items increases, like >10 items, when I enable or disable the checkbox, the other items in the recyclerview also get affected.

For Example:

If I change the checkbox at the item[5](the item ID is 21, checkbox enabling), it also firing OnCheckedChangeListener for other items like item[1](the item ID is 15, checkbox enabling) and item[8](the item ID is 18, checkbox enabling).

Logcat:

2019-04-25 11:15:33.343 9890-9890/com.hardian.mvvm.sample D/RVAdapter: setOnCheckedChange--- ID :21 isRetryEnabled; false isChecked true
2019-04-25 11:15:33.405 9890-9890/com.hardian.mvvm.sample D/RVAdapter: setOnCheckedChange--- ID :15 isRetryEnabled; false isChecked true
2019-04-25 11:15:33.415 9890-9890/com.hardian.mvvm.sample D/RVAdapter: setOnCheckedChange--- ID :18 isRetryEnabled; false isChecked true

Code: at my StatusRecyclerViewAdapter

 @Override
    public void onBindViewHolder(StatusViewHolder holder, int position) {
        StatusItem  statusItem = mDataSet.get(position);

        if (statusItem != null) {
            cbStatus.setChecked(statusItem.isRetryEnabled());

            cbStatus.setOnCheckedChangeListener((buttonView, isChecked) -> {
                Log.d("RVAdapter", "setOnCheckedChange--- ID :" + statusItem.getId()+ " isRetryEnabled; "+statusItem.isRetryEnabled()+" isChecked "+isChecked);
                //update the model via the activitiy's viewmodel through the onStatusCheckBoxChangeListener interface.
                if (onStatusCheckBoxChangeListener != null)
                    onStatusCheckBoxChangeListener.onStatusCheckBoxChanged(statusItem,isChecked);
            });

        }
    }

I need a solution to fix this issue with Recylerview recycling.

Note: I have tried this.setIsRecyclable(false); , it fixed the issue, but it is not recommended, I need to find another mechanism to avoid this.

  1. Set tag to your checkbox and get the item from the tag.
  2. Do not use setOnCheckedChangeListener, use setOnClickListener instead.

Code:

cbStatus.setTag(statusItem);
cbStatus.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        CheckBox cb = (CheckBox) v;
        StatusItem item = (StatusItem) cb.getTag();
        if (onStatusCheckBoxChangeListener != null)
            onStatusCheckBoxChangeListener.onStatusCheckBoxChanged(item,cb.isChecked());
    }
});

I think you need to get the position via holder.getAdapterPosition() . Then it should be the correct one.

Why? Because :

Note that unlike ListView, RecyclerView will not call this method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. ... If you need the position of an item later on (eg in a click listener), use getAdapterPosition() which will have the updated adapter position.

Also: setting an onClickListener inside the onBindViewHolder is not best practice, since everytime onBindViewHolder is called the listener is set. Set it in the onCreateViewHolder since onCreate is only called when a new ViewHolder has to be created and therefore much less frequently.

The trick is to set checkbox not clickable and do manual checking yourself. For this to work you have to hold its status in your data list and update it according to value from CheckBox .

First set CheckBox not clickable.

    android:clickable="false"
    android:focusable="false"
    android:focusableInTouchMode="false"


Next set up your ClickListener

  holder.cbStatus.setOnClickListener(v -> {
        StatusItem  statusItem = mDataSet.get(position);

        if (cbStatus.isChecked()) {
            cbStatus.setChecked(false);
            statusItem.setChecked(false);

        } else {
            cbStatus.setChecked(true);
            statusItem.setChecked(true);
        }
 });

you must set a else statement because the view is Recycled and uses the "old" view.

or you can make somthing like this:

    @Override
public void onBindViewHolder(StatusViewHolder holder, int position) {
    StatusItem  statusItem = mDataSet.get(position);

    youCheckBox.setChecked(false);

    if (statusItem != null) {
        cbStatus.setChecked(statusItem.isRetryEnabled());

        cbStatus.setOnCheckedChangeListener((buttonView, isChecked) -> {
            Log.d("RVAdapter", "setOnCheckedChange--- ID :" + statusItem.getId()+ " isRetryEnabled; "+statusItem.isRetryEnabled()+" isChecked "+isChecked);
            //update the model via the activitiy's viewmodel through the onStatusCheckBoxChangeListener interface.
            if (onStatusCheckBoxChangeListener != null)
                onStatusCheckBoxChangeListener.onStatusCheckBoxChanged(statusItem,isChecked);
        });

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM