Inconsistency detected in RecyclerView, How to change contents of RecyclerView while scrolling

AndroidAndroid Recyclerview

Android 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;
                }
            }
        }
);

More about: http://drakeet.me/recyclerview-bug-indexoutofboundsexception-inconsistency-detected-invalid-item-position-solution

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.

  1. Scrolling of recyclerview

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionjimmy0251View Question on Stackoverflow
Solution 1 - Androidjimmy0251View Answer on Stackoverflow
Solution 2 - AndroidCocoricoView Answer on Stackoverflow
Solution 3 - AndroiddrakeetView Answer on Stackoverflow
Solution 4 - AndroidDorukhan ArslanView Answer on Stackoverflow
Solution 5 - AndroidUdit KapahiView Answer on Stackoverflow
Solution 6 - AndroidSandip SavaliyaView Answer on Stackoverflow
Solution 7 - AndroidHiren PatelView Answer on Stackoverflow
Solution 8 - Androidkashyap jimuliyaView Answer on Stackoverflow
Solution 9 - AndroidPhill WigginsView Answer on Stackoverflow
Solution 10 - Androidmiguel_rdpView Answer on Stackoverflow
Solution 11 - AndroidSadhView Answer on Stackoverflow
Solution 12 - AndroidNhan NguyenView Answer on Stackoverflow
Solution 13 - AndroidSparshView Answer on Stackoverflow
Solution 14 - AndroidBollingView Answer on Stackoverflow
Solution 15 - AndroidDharmendraView Answer on Stackoverflow
Solution 16 - AndroidmainamView Answer on Stackoverflow
Solution 17 - AndroidVinod KolliparaView Answer on Stackoverflow
Solution 18 - Androidnunoh123View Answer on Stackoverflow
Solution 19 - AndroidyahyaView Answer on Stackoverflow
Solution 20 - Androidwl2112View Answer on Stackoverflow
Solution 21 - AndroidKrishna MeenaView Answer on Stackoverflow
Solution 22 - AndroidUmandaView Answer on Stackoverflow
Solution 23 - AndroidMoaz HView Answer on Stackoverflow
Solution 24 - AndroiddhesseView Answer on Stackoverflow
Solution 25 - AndroidMohammadHosseinBabaeiView Answer on Stackoverflow
Solution 26 - AndroidblinkmacalahanView Answer on Stackoverflow