138

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?

Mr-IDE
  • 7,051
  • 1
  • 53
  • 59
Arthur
  • 3,636
  • 5
  • 19
  • 25
  • im having this same issue now. putting the setoncheckchanged listener in the viewholder constructor give me the same error – filthy_wizard Mar 03 '16 at 09:59

24 Answers24

159

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;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Moonsoo Jeong
  • 1,605
  • 1
  • 11
  • 4
  • 1
    I see, makes sense. Wish the platform would predict such simple behaviour and give a solution instead of having to rely on flags.. – Arthur Jun 27 '15 at 08:49
  • It doesn't matter where you set the listener as long as you don't notify the `AdapterViewObserver` while `onBindViewHolder()` in progress. – Yaroslav Mytkalyk Jan 06 '16 at 16:54
  • 7
    I prefer this solution http://stackoverflow.com/a/32373999/1771194 with some improvements in comment. It also allowed me make "RadioGroup" in RecyclerView. – Artem Jan 07 '16 at 11:22
  • how do i get the items postion in my list?? – filthy_wizard Mar 01 '16 at 16:58
  • 2
    this doesnt work for me in the viewholder. still get the crash error. i need to change vars in the arraylist. very strange. no too sure where i can attach the listiner. – filthy_wizard Mar 03 '16 at 10:02
  • @user1232726 I don't think this deserves the accepted answer. Inside bindViewHolder you can still call notifyDataSetChanged and notifyItemChanged. set null on setCheckedChangeListener(null) and holder.checkBox.setChecked(model.getPosition.isSelected) then call holder.checkBox.setCheckedOnchangeListener(new OncheckedChangeListner) inside this you can call notify....notifyChan.... methods. – EngineSense Apr 05 '16 at 09:02
  • You do not need to move the 'setOnCheckedChangeListener()' to the ViewHolder constructor. There is an alternate solution, which is the same solution as @ArtemK recommends: stackoverflow.com/a/32373999/1771194 . Moonsoo's answer has a good explanation of the problem, but the english was hard to understand. So I put my own explanation in my own answer: http://stackoverflow.com/a/37305564/2423194 – Rock Lee May 18 '16 at 16:49
  • i follow this flow and still getting error the problem is that i'm recycling adapter with multiple objects to improve memory usage, but this flow not is the appropiate. – Ninja Coding Nov 10 '16 at 18:19
  • Thanks for clarification, but instead of using _flag_, In `onBindViewHolder` method, I would first unset the `OnCheckChangeListener` of checkbox then -> `mCheckBox.setChecked(trueOrFalse);` and then -> set the `OnCheckChangeListener` again. – Akoder Aug 22 '17 at 06:14
  • clearly onCheckedChanged is not meant to be used to update data sets. – Maciej Beimcik Jan 02 '18 at 13:28
  • For me, I had a `IndexOutOfBoundsException` in `onBindViewHolder` when I was trying to get an item from my `List` where index was out of range. This answer inspired me to debug my `onBindViewHolder` method. – levibostian Sep 12 '18 at 14:50
  • @filthy_wizard you can get Item positions by calling `getAdapterPosition()` – Ramiz Ansari Oct 18 '18 at 01:54
50

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);
    }
JoniDS
  • 511
  • 4
  • 7
  • 2
    Good answer, but it is better to not create listener on each onBindViewHolder call. Make it as a field. – Artem Jan 07 '16 at 11:20
  • 1
    Using a field is of course better, I was just giving an example that works. But thanks for the warning, I will update the answer. – JoniDS Jan 08 '16 at 12:01
  • 1
    I actually need to bind a new listener each time anyways, because the listener needs an updated position variable each time. So this is a great answer so I do not have to use a Handler. – Rock Lee May 17 '16 at 16:28
  • This is definitely the best way of doing it as it's never recommended to keep global state, which the accepted answer (https://stackoverflow.com/a/31069171/882251) is recommending. – Darwind Mar 14 '18 at 20:35
  • Simplest one !! Thanks !! – DalveerSinghDaiya Mar 29 '18 at 07:50
40

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

cybergen
  • 3,108
  • 1
  • 22
  • 30
  • 3
    That's the right answer, you cannot change item while it is setting (with calling onBindViewHolder). In that case you have to call notifyDataSetChanged at the end of current loop by calling Handler.post() – pjanecze Feb 13 '15 at 10:44
  • 1
    @user1232726 If you create the Handler on the main thread, you don't have to specify a Looper (defaults to the calling threads looper). So yes, this is my advice. Otherwise you can also specify the Looper manually. – cybergen Mar 03 '16 at 10:21
  • unfortunately my checkboxes dont remain checked when i scroll down and scroll back up again. boo. lol – filthy_wizard Mar 03 '16 at 10:23
  • @user1232726 search for an answer or ask a new question describing your issue. – cybergen Mar 03 '16 at 17:50
  • You can actually use the default handler from `getActivity().getWindow().getItemDecor().getHandler()` or something like that. No need to create a new one, at least it works for me. – tufekoi Apr 18 '16 at 15:45
  • 2
    I would strongly **discourage** this answer as this is a hacky way to solve the problem. More you do this more your code becomes complex to understand. Refer [Moonsoo's answer](http://stackoverflow.com/a/31069171/1282812) to understand the problem and [JoniDS's](http://stackoverflow.com/a/32373999/1282812) answer to solve the problem. – Kalpesh Patel Jun 11 '16 at 20:50
27

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!

Randika Vishman
  • 7,983
  • 3
  • 57
  • 80
jigar
  • 307
  • 2
  • 11
  • 1
    Nice work) BUT only on click( if i slow move widget (SwitchCompat), this action will be missed.This is the only issue – Vlad Aug 29 '16 at 18:13
13
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);
                }
            }
        });
    }
bruce
  • 189
  • 2
  • 6
  • I suppose that you can easily notify adapter twice. – Maxim Petlyuk Feb 12 '20 at 11:05
  • This is the only answer that actually ends up performing the calculation instead of just ignoring it if when it tried it wasn't ready. Though, this won't cause an out of memory error for such a large call stack?. How long does it take for post to call the next recursive call? – Sam Oct 15 '21 at 11:45
8

Found a simple solution -

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    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.

PhilLab
  • 4,777
  • 1
  • 25
  • 77
Rohan Kandwal
  • 9,112
  • 8
  • 74
  • 107
8

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);
    }
});
lukess
  • 964
  • 1
  • 14
  • 19
Antoine Draune
  • 323
  • 1
  • 3
  • 12
5

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());
            }
        }
    });
}
Community
  • 1
  • 1
Rock Lee
  • 9,146
  • 10
  • 55
  • 88
  • You should avoid initializing the OnCheckedChangeListener again and again in the onBindViewHolder (less GC needed this way). This is supposed to be called in onCreateViewHolder , and you get the position by calling holder.getAdapterPosition() . – android developer Feb 19 '17 at 08:36
5

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();
                    }
                });
Mohammad Reza Norouzi
  • 5,899
  • 1
  • 16
  • 18
5

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

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    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();
                }
            }
        });
    }
}
NcJie
  • 812
  • 7
  • 14
2

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).

yigit
  • 37,683
  • 13
  • 72
  • 58
  • Thanks @yigit! My issue didn't have to do with a checkbox but a more complex situation where I had to notify a different item in the adapter, but I was getting a similar crash. I updated my notification logic to only update when the data is actually changing and it resolved my crash. So my new rule with RecyclerViews: don't notify that something changed when nothing changed. Many thanks for this answer! – CodyEngel Jul 10 '17 at 19:27
2

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();
        }
    });
Krishan Kumar Mourya
  • 2,207
  • 3
  • 26
  • 31
1

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.

Andrew
  • 451
  • 1
  • 4
  • 10
1

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

Amir Hossein Ghasemi
  • 20,623
  • 10
  • 57
  • 53
1

Simple use Post:

new Handler().post(new Runnable() {
        @Override
        public void run() {
                mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
            }
        }
    });
Kai Wang
  • 3,303
  • 1
  • 31
  • 27
1

simply use isPressed() method of CompoundButton in onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
e.g

public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {   
                      ... //your functionality    
                            if(compoundButton.isPressed()){
                                notifyDataSetChanged();
                            }
                        }  });
Asad
  • 1,241
  • 3
  • 19
  • 32
1

mostly it happen beacause notifydatasetchanged calling onCheckedchanged event of checkbox and in that event again there is notifydatasetchanged.

to solve it you can just check that checkbox is checked by programatically or user pressed it. there is method isPressed for it.

so wrap whole listner code inside isPressed method. and its done.

 holder.mBinding.cbAnnual.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {

                if(compoundButton.isPressed()) {


                       //your code
                        notifyDataSetChanged();   

            }
        });
Soham Pandya
  • 386
  • 1
  • 14
1

I suffered with this problem for hour and this is how you can fix it. But before you began there are some conditions to this solution.

MODEL CLASS

public class SelectUserModel {

    private String userName;
    private String UserId;
    private Boolean isSelected;


    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserId() {
        return UserId;
    }

    public void setUserId(String userId) {
        UserId = userId;
    }

    public Boolean getSelected() {
        return isSelected;
    }

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

CHECKBOX in ADAPTER CLASS

CheckBox cb;

ADAPTER CLASS CONSTRUCTOR & LIST OF MODEL

private List<SelectUserModel> userList;

public StudentListAdapter(List<SelectUserModel> userList) {
        this.userList = userList;

        for (int i = 0; i < this.userList.size(); i++) {
            this.userList.get(i).setSelected(false);
        }
    }

ONBINDVIEW [Please use onclick in place of onCheckChange]

public void onBindViewHolder(@NonNull final StudentListAdapter.ViewHolder holder, int position) {
    holder.cb.setChecked(user.getSelected());
    holder.cb.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            int pos = (int) view.getTag();
            Log.d(TAG, "onClick: " + pos);
            for (int i = 0; i < userList.size(); i++) {
                if (i == pos) {
                    userList.get(i).setSelected(true);
// an interface to listen to callbacks
                    clickListener.onStudentItemClicked(userList.get(i));
                } else {
                    userList.get(i).setSelected(false);
                }
            }
            notifyDataSetChanged();
        }
    });

}

Divyanshu Kumar
  • 1,272
  • 15
  • 15
0

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);
}
Alécio Carvalho
  • 13,481
  • 5
  • 68
  • 74
0
        @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 < getStudentList.size(); i++) {

                            getStudentList.get(i).setSelected(false);

                        }
                        getStudentList.get(position).setSelected(checkBox.isChecked());

                        notifyDataSetChanged();
                    } else {

                    }

                }
            };
        }
0

I had the same problem using the Checkbox and the RadioButton. Replacing notifyDataSetChanged() with notifyItemChanged(position) worked. I added a Boolean field isChecked to the data model. Then I updated the Boolean value and in onCheckedChangedListener, I called notifyItemChanged(adapterPosition). This might not be the best way, but worked for me. The boolean value is used for checking whether the item is checked.

0
 override fun bindItemViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
    var item: ThingChannels = items[position]
    rowActionBinding.xCbChannel.text = item.channelType?.primaryType
    rowActionBinding.xTvRoomName.text = item.thingDetail?.room?.name
    rowActionBinding.xCbChannel.isChecked = item.isSelected
    rowActionBinding.xCbChannel.tag = position
    rowActionBinding.xCbChannel.setOnClickListener {
        setSelected(it.tag as Int)
        if (onItemClickListener != null) {
            onItemClickListener!!.onItemClick(position, null)
        }
    }
}
Nalawala Murtuza
  • 4,605
  • 1
  • 12
  • 21
0

None of the past answers solve the problem!

The problem with past answers

All of them either avoid the problem by swallowing the change (i.e. not notifying the adapter of the change) if the user is scrolling, which just means that if the user was scrolling when the change was ready, they will never see the change. Or, they suggest using recylerView.post() which just postpones the problem.

The answer

Option #1

Stop the scrolling and then notify the adapter:

recyclerView.stopScroll()
val copy = workingList.toList()

//prevent IndexOutOfBoundsException: Inconsistency detected... (https://stackoverflow.com/questions/41054959/java-lang-indexoutofboundsexception-inconsistency-detected-invalid-view-holder)
workingList.clear()
recyclerViewAdapter?.notifyDataSetChanged()
//set display to correct data 
workingList.addAll(copy)
recyclerViewAdapter?.notifyItemRangeInserted(0, workingList.size)

Option #2

For a better user experience, you can let them continue scrolling and listen for when they stop scrolling in order to update the UI, but this method should only be used if you do not plan on writing a function that will accept different RecyclerView instances, because if you add multiple listeners to the same RecyclerView which are all trying to be updated, the app will crash:

if(recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) //notify RecyclerView
else recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(
            recyclerView: RecyclerView,
            newState: Int
        ) {
            if(newState == RecyclerView.SCROLL_STATE_IDLE) {
                //notify RecyclerView...

                val copy = workingList.toList()
                
                //prevent IndexOutOfBoundsException: Inconsistency detected... (https://stackoverflow.com/questions/41054959/java-lang-indexoutofboundsexception-inconsistency-detected-invalid-view-holder)
                workingList.clear()
                recyclerViewAdapter?.notifyDataSetChanged()
                //set to display correct data
                workingList.addAll(copy)
                recyclerViewAdapter?.notifyItemRangeInserted(0, workingList.size)
            }
        })

Note: you can use option #2 even if you are writing a util function by keeping a list of previously registered instances and not add a listener if it has already been registered, but it is not "clean coding" to rely on state in a library/util class.

Sam
  • 261
  • 2
  • 11
-2

For me problem occurred when I exited from EditText by Done, Back, or outside input touch. This causes to update model with input text, then refresh recycler view via live data observing.

The Problem was that cursor/focus remain in EditText.

When I have deleted focus by using:

editText.clearFocus() 

Notify data changed method of recycler view did stop throwing this error.

I think this is one of the possible reason/solutions to this problem. It is possible that this exception can be fixed in another way as it can be caused by totally different reason.

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143