Inconsistency detected in RecyclerView, How to change contents of RecyclerView while scrolling
AndroidAndroid RecyclerviewAndroid Problem Overview
I'm using RecyclerView
to display name of the items. My row contains single TextView
. Item names are stored in List<String> mItemList
.
To change contents of RecyclerView
, I replace Strings in mItemList
and call notifyDataSetChanged
() on RecyclerViewAdapter
.
But If I try to change contents of the mItemList
while RecyclerView is scrolling, sometimes it gives me
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588
This happens if size of mItemList
is less than before. So what is the correct way to change contents of the RecyclerView
? Is this a bug in RecyclerView
?
Here's full stack trace of Exception:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3300)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3258)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1803)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1302)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1265)
at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1093)
at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:956)
at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:2715)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
at android.view.Choreographer.doCallbacks(Choreographer.java:555)
at android.view.Choreographer.doFrame(Choreographer.java:524)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4921)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1027)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)
at dalvik.system.NativeStart.main(Native Method)
AdapterView code:
private static class FileListAdapter extends RecyclerView.Adapter<FileHolder> {
private final Context mContext;
private final SparseBooleanArray mSelectedArray;
private final List<String> mList;
FileListAdapter(Context context, List<String> list, SparseBooleanArray selectedArray) {
mList = list;
mContext = context;
mSelectedArray = selectedArray;
}
@Override
public FileHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.file_list_item, viewGroup, false);
TextView tv = (TextView) view
.findViewById(R.id.file_name_text);
Typeface font = Typeface.createFromAsset(viewGroup.getContext().getAssets(),
viewGroup.getContext().getString(R.string.roboto_regular));
tv.setTypeface(font);
return new FileHolder(view, tv);
}
@Override
public void onBindViewHolder(FileHolder fileHolder, final int i) {
String name = mList.get(i);
// highlight view if selected
setSelected(fileHolder.itemView, mSelectedArray.get(i));
// Set text
fileHolder.mTextView.setText(name);
}
@Override
public int getItemCount() {
return mList.size();
}
}
private static class FileHolder extends RecyclerView.ViewHolder {
public final TextView mTextView;
public FileHolder(View itemView, TextView tv) {
super(itemView);
mTextView = tv;
}
}
Android Solutions
Solution 1 - Android
Edit: The bug is fixed now, if you're still getting the same Exception, please make sure you're updating your Adapter data source only from the main thread and calling appropriate adapter notify method after it.
Old answer: It seems to be a bug in RecyclerView
, it's reported here and here. Hopefully it will be fixed in the next release.
Solution 2 - Android
No problem for me. Use NotifyDataSetChanged();
public class MyFragment extends Fragment{
private MyAdapter adapter;
// Your code
public void addArticle(){
ArrayList<Article> list = new ArrayList<Article>();
//Add one article in this list
adapter.addArticleFirst(list); // or adapter.addArticleLast(list);
}
}
public class ArticleAdapterRecycler extends RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder> {
private ArrayList<Article> Articles = new ArrayList<Article>();
private Context context;
// Some functions from RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder>
// Add at the top of the list.
public void addArticleFirst(ArrayList<Article> list) {
Articles.addAll(0, list);
notifyDataSetChanged();
}
// Add at the end of the list.
public void addArticleLast(ArrayList<Article> list) {
Articles.addAll(Articles.size(), list);
notifyDataSetChanged();
}
}
Solution 3 - Android
Just prohibit RecyclerView's scroll when data is changing.
Like as my code:
mRecyclerView.setOnTouchListener(
new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsRefreshing) {
return true;
} else {
return false;
}
}
}
);
Solution 4 - Android
Although a couple of useful hyperlinks about this issue are given in the accepted answer, it is not true that this behavior of RecyclerView
while scrolling is a bug.
If you see this exception, most probably you forget to notify the adapter after the content of RecyclerView
is "changed". People call notifyDataSetChanged()
only after an item is added to the data set. However, the inconsistency occurs not only after you refill the adapter, but also when you remove an item or you clear the data set, you should refresh the view by notifying the adapter about this change:
public void refillAdapter(Item item) {
adapter.add(item);
notifyDataSetChanged();
}
public void cleanUpAdapter() {
adapter.clear();
notifyDataSetChanged(); /* Important */
}
In my case, I tried to clean up the adapter in onStop()
, and refill it in onStart()
. I forgot to call notifyDataSetChanged()
after the adapter is cleaned by using clear()
. Then, whenever I changed the state from onStop()
to onStart()
and swiftly scrolled the RecyclerView
while the data set is reloading, I saw this exception. If I waited the end of reloading without scrolling, there would be no exception since the adapter can be reinstated smoothly this time.
In short, the RecyclerView
is not consistent when it is on view change. If you try to scroll the view while the changes in the data set are processed, you see java.lang.IndexOutOfBoundsException: Inconsistency detected
. To eliminate this problem, you should notify the adapter immediately after the data set is changed.
Solution 5 - Android
The problem is definitely not because of recyclerview scrolling, but it is related to notifyDataSetChanged(). I had a recycler view in which i was constantly changing data i.e. adding and removing data. I was calling notifyDataSetChanged() everytime I was adding items to my list, But was not refreshing the adapter whenever the item is being removed or the list was cleared.
So to fix the :
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:12 at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5456)
I called adapter.notifyDataSetChanged() after list.clear(), wherever it was required.
if (!myList.isEmpty()) {
myList.clear();
myListAdapter.notifyDataSetChanged();
}
Since then, I never encountered the exception. Hope it works out the same for others as well. :)
Solution 6 - Android
This problem is coming to recyclerview if you use
adapter.setHasStableIds(true);
if you set so, remove this, and to update your dataset inside adaptor;
if you still face the issue, invalidate all views once you get new data and then update your dataset.
Solution 7 - Android
I was facing same issue, java.lang.IndexOutOfBoundsException: Inconsistency detected.
Create Custom LinearLayoutManager.
HPLinearLayoutManager.java
public class HPLinearLayoutManager extends LinearLayoutManager {
public HPLinearLayoutManager(Context context) {
super(context);
}
public HPLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public HPLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Magic here
*/
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
}
create instance of HPLinearLayoutManager
.
HPLinearLayoutManager hpLinearLayoutManager = new HPLinearLayoutManager(mContext);
recyclerView.setLayoutManager(hpLinearLayoutManager);
Hope this would help you.
Solution 8 - Android
I am altering data for the RecyclerView
in the background Thread
. I got the same Exception
as the OP. I added this after changing data:
myRecyclerView.post(new Runnable() { @Override public void run() { myRecyclerAdapter.notifyDataSetChanged(); } });
Hope it helps
Solution 9 - Android
I had a similar issue but with deleting the contents. I also wanted to keep the animations too. I ended up using the notifyRemove, then passing the range. This seems to fix any issues...
public void deleteItem(int index) {
try{
mDataset.remove(index);
notifyItemRemoved(index);
} catch (IndexOutOfBoundsException e){
notifyDataSetChanged();
e.printStackTrace();
}
}
Seems to be working and getting rid of the IOB Exception...
Solution 10 - Android
I got this to work using Cocorico suggestion in a previous answer (https://stackoverflow.com/a/26927186/3660638) but there's a catch: since I'm using a SortedList
, using notifyDataSetChanged() everytime there's a change in data (add, remove, etc) makes you lose the item animations that you get with notifyItemXXXXX(position)
, so what I ended up doing was using it only when I change the data in batch, like:
public void addAll(SortedList<Entity> items) {
movieList.beginBatchedUpdates();
for (int i = 0; i < items.size(); i++) {
movieList.add(items.get(i));
}
movieList.endBatchedUpdates();
notifyDataSetChanged();
}
Solution 11 - Android
You must use in your getitem count
public int getItemCount() {
if (mList!= null)
return mList.size();
else
return 0;
}
Also refreshing the recycler view please use this
if (recyclerView.getAdapter() == null) {
recyclerView.setHasFixedSize(true);
mFileListAdapter= new FileListAdapter(this);
recyclerView.setAdapter(mFileListAdapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
} else {
mFileListAdapter.notifyDataSetChanged();
}
By using this solution you are not able to solve the issue you simply use a condition inside the onBindViewHolder to resolve the java.lang.IndexOutOfBoundsException
public void onBindViewHolder(FileHolder fileHolder, final int i) {
if(i < mList.size)
{
String name = mList.get(i);
setSelected(fileHolder.itemView, mSelectedArray.get(i));
fileHolder.mTextView.setText(name);
}
}
Solution 12 - Android
create CustomLinearLayoutManager:
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager(Context context) {
super(context);
}
public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
return super.scrollVerticallyBy(dy, recycler, state);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
Solution 13 - Android
I figure out that, for me this exception comes when two things happens at the same time i.e.
-
Scrolling of recyclerview
-
data set getting changed
So, I solved this problem by disabling the scroll till the notifydatasetchanged is called.
leaderAdapter.notifyDataSetChanged();
pDialog.hide();
To disable the scroll, I have used a progress dialog, whose setCancelable is false.
pDialog = new ProgressDialog(getActivity());
pDialog.setMessage("Please wait...");
pDialog.setCancelable(false);
The trick here is to enable the scrolling only when the data set has been updated.
Solution 14 - Android
Avoid notifyDatasetHasChanged() and do the following:
public void setItems(ArrayList<Article> newArticles) {
//get the current items
int currentSize = articles.size();
//remove the current items
articles.clear();
//add all the new items
articles.addAll(newArticles);
//tell the recycler view that all the old items are gone
notifyItemRangeRemoved(0, currentSize);
//tell the recycler view how many new items we added
notifyItemRangeInserted(0, articles.size());
}
Solution 15 - Android
I had same issue when I was setting new adapter instance on based on some selection criteria.
I have fixed my issue by using RecyclerView.swapAdapter(adapter, true)
When we set new adapter.
Solution 16 - Android
I have same issue with this problem, I'm very tired to search and resolve it. But I have found answer to resolve and exceptions have not been thrown out again.
public class MyLinearLayoutManager extends LinearLayoutManager
{
public MyLinearLayoutManager(Context context) {
super(context);
}
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public MyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//override this method and implement code as below
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I hope this answer will be resolve your problem.
Solution 17 - Android
I have replicated this issue. This happened when we remove items in the background thread from mList but dont call notifyDataSetChanged(). Now If we scroll This exception is comming.
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 86(offset:86).state:100
Initially I had 100 items and removed few items from background thread.
Seems like Recyclerview calls getItemCount() itself to validate the state.
Solution 18 - Android
I dont see anthing wrong with the code you posted. The only thing weird to me is this line
setSelected(fileHolder.itemView, mSelectedArray.get(i));
in your onBindViewHolder method in the adapter.. are you updating this array too when you change the size of your list of items in the array?
Solution 19 - Android
I had similar problem while i try to add first item into recyclerView with notifyItemInserted method, so i modified addItem function on my adapter as below and it resolved.
Weird problem, hope that it'll be fixed soon thoug!
public void addItem(int position, TableItem item) {
boolean firstEntry = false;
if (items.size() == 0) {
firstEntry = true;
}
items.add(position, item);
if (firstEntry) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
}
}
Solution 20 - Android
there is one sentence in sound code : /** * Used when LayoutState is constructed in a scrolling state. It should * be set the amount of scrolling we can make without creating a new * view. Settings this is required for efficient view recycling. */ int mScrollingOffset;
Solution 21 - Android
In my case it solved by changing
mRecyclerView.smoothScrollToPosition(0)
to
mRecyclerView.scrollToPosition(0)
Solution 22 - Android
I also had the same issue and I have fixed it with not using notifyItemRangeChanged() method. It is nicely explained at
https://code.google.com/p/android/issues/detail?id=77846#c10
Solution 23 - Android
try to use a boolean flag, initialize it as false and inside OnRefresh method make it true, clear your dataList if flag is true just before adding the new data to it and after that make it false.
your code might be like this
private boolean pullToRefreshFlag = false ;
private ArrayList<your object> dataList ;
private Adapter adapter ;
public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{
private void requestUpdateList() {
if (pullToRefresh) {
dataList.clear
pullToRefreshFlag = false;
}
dataList.addAll(your data);
adapter.notifyDataSetChanged;
@Override
OnRefresh() {
PullToRefreshFlag = true
reqUpdateList() ;
}
}
Solution 24 - Android
In my case the problem was me.
My setup is a Recyclerview, Adapter & Cursor/Loader mechanism.
At one point in my App the loader is destroyed.
supportLoaderManager.destroyLoader(LOADER_ID_EVENTS)
I was expecting the Recyclerview would display an empty list since i just deleted their datasource. What makes the error finding more complicated was, that the list was visible and the well known Exception occured only on a fling/scroll/animation.
That cost me a few hrs. :)
Solution 25 - Android
do this when you want to add view(like notifyData or addView or something like that)
if(isAdded()){
//
// add view like this.
//
// celebrityActionAdapter.notifyItemRangeInserted(pageSize, 10);
//
//
}
Solution 26 - Android
I recently ran into this issue and discovered my problem was I was modifying the adapter's data source on one loop/call stack and then calling notifyDataSetChanged
on a subsequent loop/call stack. Between changing the data source and notifyDataSetChanged
occurring, the RecyclerView
was trying to fill in views due to the scrolling and noticed the adapter was in a weird state and justifiably threw this exception.
Yigit Boyar explains over and over again two reasons why this crash would occur in your app:
- You must be on the same call stack when you change your adapter source and
notifyDataSetChanged()
- You must be on the Main Thread when changing your adapter's data source
If you're unsure how to debug this do, add the following Kotlin code where you change your adapter source and where you call notifyDataSetChanged
Log.d("TEST", "isMainThread: ${Looper.myLooper() == Looper.getMainLooper()}")
Log.d("TEST", Log.getStackTraceString(Exception("Debugging RV and Adapter")))