Android RecyclerView : notifyDataSetChanged() IllegalStateException

匿名 (未验证) 提交于 2019-12-03 02:52:02

问题:

I'm trying to update the items of a recycleview using notifyDataSetChanged().

This is my onBindViewHolder() method in the recycleview adapter.

@Override public void onBindViewHolder(ViewHolder viewHolder, int position) {       //checkbox view listener     viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {         @Override         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {              //update list items             notifyDataSetChanged();         }     }); } 

What I want to do is update the list items, after I check a checkbox. I get an illegal exception though: "Cannot call this method while RecyclerView is computing a layout or scrolling"

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling     at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:1462)     at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:2982)     at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:7493)     at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:4338)     at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.java:111) 

I also used notifyItemChanged(), same exception. Any secret way to update to notify the adapter that something changed?

回答1:

You should move method 'setOnCheckedChangeListener()' to ViewHolder which is inner class on your adapter.

onBindViewHolder() is not a method that initialize ViewHolder. This method is step of refresh each recycler item. When you call notifyDataSetChanged(), onBindViewHolder() will be called as the number of each item times.

So If you notifyDataSetChanged() put into onCheckChanged() and initialize checkBox in onBindViewHolder(), you will get IllegalStateException because of circular method call.

click checkbox -> onCheckedChanged() -> notifyDataSetChanged() -> onBindViewHolder() -> set checkbox -> onChecked...

Simply, you can fix this by put one flag into Adapter.

try this,

private boolean onBind;  public ViewHolder(View itemView) {     super(itemView);     mCheckBox = (CheckBox) itemView.findViewById(R.id.checkboxId);     mCheckBox.setOnCheckChangeListener(this); }  @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {     if(!onBind) {         // your process when checkBox changed         // ...          notifyDataSetChanged();     } }  ...  @Override public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {     // process other views      // ...      onBind = true;     viewHolder.mCheckBox.setChecked(trueOrFalse);     onBind = false; } 


回答2:

Using a Handler for adding items and calling notify...() from this Handler fixed the issue for me.



回答3:

You can just reset the previous listener before you make changes and you won't get this exception.

private CompoundButton.OnCheckedChangeListener checkedListener = new CompoundButton.OnCheckedChangeListener() {                                               @Override                         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {                             //Do your stuff                     });;      @Override     public void onBindViewHolder(final ViewHolder holder, final int position) {         holder.checkbox.setOnCheckedChangeListener(null);         holder.checkbox.setChecked(condition);         holder.checkbox.setOnCheckedChangeListener(checkedListener);     } 


回答4:

I don't know well, but I also had same problem. I solved this by using onClickListner on checkbox

viewHolder.mCheckBox.setOnClickListener(new OnClickListener() {         @Override         public void onClick(View v) {             // TODO Auto-generated method stub             if (model.isCheckboxBoolean()) {                 model.setCheckboxBoolean(false);                 viewHolder.mCheckBox.setChecked(false);             } else {                 model.setCheckboxBoolean(true);                 viewHolder.mCheckBox.setChecked(true);             }             notifyDataSetChanged();         }     }); 

Try this, this may help!



回答5:

protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView, final RecyclerView.Adapter adapter) {         handler.post(new Runnable() {             @Override             public void run() {                 if (!recyclerView.isComputingLayout()) {                     adapter.notifyDataSetChanged();                 } else {                     postAndNotifyAdapter(handler, recyclerView, adapter);                 }             }         });     } 


回答6:

At first I thought Moonsoo's answer (the accepted answer) wouldn't work for me because I cannot initialize my setOnCheckedChangeListener() in the ViewHolder constructor because I need to bind it each time so it gets an updated position variable. But it took me a long time to realize what he was saying.

Here is an example of the "circular method call" he is talking about:

public void onBindViewHolder(final ViewHolder holder, final int position) {     SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);     mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {                 @Override                 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {                        if (isChecked) {                            data.delete(position);                            notifyItemRemoved(position);                            //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!                            notifyItemRangeChanged(position, data.size());                        }                    }             });     //Set the switch to how it previously was.     mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop. } 

The only problem with this, is that when we need to initialize the switch to be on or off (from past saved state, for example), it is calling the listener which might call nofityItemRangeChanged which calls onBindViewHolder again. You cannot call onBindViewHolder when you are already in onBindViewHolder], because you cannot notifyItemRangeChanged if you are already in the middle of notifying that the item range has changed. But I only needed to update the UI to show it on or off, not wanting to actually trigger anything.

Here is the solution I learned from JoniDS's answer that will prevent the infinite loop. As long as we set the listener to "null" before we set Checked, then it will update the UI without triggering the listener, avoiding the infinite loop. Then we can set the listener after.

JoniDS's code:

holder.checkbox.setOnCheckedChangeListener(null); holder.checkbox.setChecked(condition); holder.checkbox.setOnCheckedChangeListener(checkedListener); 

Full solution to my example:

public void onBindViewHolder(final ViewHolder holder, final int position) {     SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);      //Set it to null to erase an existing listener from a recycled view.     mySwitch.setOnCheckedChangeListener(null);      //Set the switch to how it previously was without triggering the listener.     mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.      //Set the listener now.     mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {         @Override         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {             if (isChecked) {                 data.delete(position);                 notifyItemRemoved(position);                 //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!                 notifyItemRangeChanged(position, data.size());             }         }     }); } 


回答7:

your CheckBox item is in changing drawable when you call notifyDataSetChanged(); so this exception would be occurred. Try call notifyDataSetChanged(); in post of your view. For Example:

buttonView.post(new Runnable() {                     @Override                     public void run() {                         notifyDataSetChanged();                     }                 }); 


回答8:

Found a simple solution -

public class MyAdapter extends RecyclerView.Adapter{      private RecyclerView mRecyclerView;       @Override     public void onAttachedToRecyclerView(RecyclerView recyclerView) {         super.onAttachedToRecyclerView(recyclerView);         mRecyclerView = recyclerView;     }      private CompoundButton.OnCheckedChangeListener checkedChangeListener      = (compoundButton, b) -> {         final int position = (int) compoundButton.getTag();         // This class is used to make changes to child view         final Event event = mDataset.get(position);         // Update state of checkbox or some other computation which you require         event.state = b;         // we create a runnable and then notify item changed at position, this fix crash         mRecyclerView.post(new Runnable() {             @Override public void run() {                 notifyItemChanged(position));             }         });     } } 

Here we create a runnable to notifyItemChanged for a position when recyclerview is ready to handle it.



回答9:

While item is being bound by the layout manager, it is very likely that you are setting the checked state of your checkbox, which is triggering the callback.

Of course this is a guess because you did not publish the full stack trace.

You cannot change adapter contents while RV is recalculating the layout. You can avoid it by not calling notifyDataSetChanged if item's checked state is equal to the value sent in the callback (which will be the case if calling checkbox.setChecked is triggering the callback).



回答10:

Use onClickListner on checkbox instead of OnCheckedChangeListener, It will solve the problem

viewHolder.myCheckBox.setOnClickListener(new OnClickListener() {         @Override         public void onClick(View v) {             if (viewHolder.myCheckBox.isChecked()) {                 // Do something when checkbox is checked             } else {                 // Do something when checkbox is unchecked                             }             notifyDataSetChanged();         }     }); 


回答11:

Before notifyDataSetChanged() just check that with this method: recyclerView.IsComputingLayout()



回答12:

Simple use Post:

new Handler().post(new Runnable() {         @Override         public void run() {                 mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);             }         }     }); 


回答13:

When you have the Message Error:

Cannot call this method while RecyclerView is computing a layout or scrolling 

Simple, Just do what cause the Exception in:

RecyclerView.post(new Runnable() {     @Override     public void run() {         /**          ** Put Your Code here, exemple:         **/         notifyItemChanged(position);     } }); 


回答14:

I ran into this exact issue! After Moonsoo's answer didn't really float my boat, I messed around a bit and found a solution that worked for me.

First, here's some of my code:

    @Override     public void onBindViewHolder(ViewHolder holder, final int position) {      final Event event = mDataset.get(position);      //     //  .......     //      holder.mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {         @Override         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {             event.setActive(isChecked);             try {                 notifyItemChanged(position);             } catch (Exception e) {                 Log.e("onCheckChanged", e.getMessage());             }         }     }); 

You'll notice I'm specifically notifying the adapter for the position I'm changing, instead of the entire dataset like you're doing. That being said, although I can't guarantee this will work for you, I resolved the problem by wrapping my notifyItemChanged() call in a try/catch block. This simply caught the exception, but still allowed my adapter to register the state change and update the display!

Hope this helps someone!

EDIT: I'll admit, this probably is not the proper/mature way of handle the issue, but since it doesn't appear to be causing any problems by leaving the exception unhandled, I thought I'd share in case it was good enough for someone else.



回答15:

This is happening because you're probably setting the 'listener' before you configure the value for that row, which makes the listener to get triggered when you 'configure the value' for the checkbox.

What you need to do is:

@Override public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {    viewHolder.mCheckBox.setOnCheckedChangeListener(null);    viewHolder.mCheckBox.setChecked(trueOrFalse);    viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener); } 


回答16:

Why not checking the RecyclerView.isComputingLayout() state as follows?

public class MyAdapter extends RecyclerView.Adapter{      private RecyclerView mRecyclerView;       @Override     public void onAttachedToRecyclerView(RecyclerView recyclerView) {         super.onAttachedToRecyclerView(recyclerView);         mRecyclerView = recyclerView;     }      @Override     public void onBindViewHolder(ViewHolder viewHolder, int position) {          viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {             @Override             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {                 if (mRecyclerView != null && !mRecyclerView.isComputingLayout()) {                     notifyDataSetChanged();                 }             }         });     } } 


回答17:

        @Override         public void onBindViewHolder(final MyViewHolder holder, final int position) {             holder.textStudentName.setText(getStudentList.get(position).getName());             holder.rbSelect.setChecked(getStudentList.get(position).isSelected());             holder.rbSelect.setTag(position); // This line is important.             holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));          }          @Override         public int getItemCount() {             return getStudentList.size();         }         private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {             return new View.OnClickListener() {                 @Override                 public void onClick(View v) {                     if (checkBox.isChecked()) {                         for (int i = 0; i 


回答18:

For me i was listening to change in rating of a rating bar but on long press of multiple clicks at once app was crashing due to the issue then found a clear solution if i wanted to notifydatasetchange(); in bindviewholder handled it with handler given by:

//inside bindViewHolder                  new Handler().post(new Runnable() {                     @Override                     public void run() {                         notifyDataSetChanged();                     }                 }); 

Hope so it will resolve the issue.



回答19:

use DiffUtil instead to get better performance



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!