64

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;
    }
}
Hiren Patel
  • 52,124
  • 21
  • 173
  • 151
jimmy0251
  • 16,293
  • 10
  • 36
  • 39

26 Answers26

43

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.

jimmy0251
  • 16,293
  • 10
  • 36
  • 39
  • No workaround until the next version will be released? – Víctor Albertos Jan 02 '15 at 13:19
  • 3
    Still facing the problem .. :( – theapache64 May 29 '15 at 05:10
  • I suspect this has something to do with having the RecyclerView inside of a ScrollView. I also experienced it but not sure. Try changing the layout and see if this helps? – revolutionary Jun 23 '15 at 13:56
  • 19
    i am using compile 'com.android.support:recyclerview-v7:23.0.0' but still facing the same issue.? –  Aug 19 '15 at 15:25
  • 2
    You will face the same issue if you try to change your list contents from background thread. – jimmy0251 Dec 11 '15 at 14:42
  • 1
    @jimmy0251 still unreolved :D i have faound a work around for this, but kindly why can't we change the list from another thread/activity/fragment and adapter can take take of it over all we are calling notifyDataSet... sigh..... – mfaisalhyder Jun 12 '16 at 16:30
  • It is not a bug and I think it will not be fixed in the next releases. See my answer please. – Dorukhan Arslan Jul 06 '16 at 17:46
  • this is not the solution as the people still getting this error and according this accepted answer there is no error anymore isn't it a contradictory answer – Ashwini Saini Jan 23 '20 at 14:03
17

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();
    }
}
WarrenFaith
  • 57,492
  • 25
  • 134
  • 150
Cocorico
  • 1,998
  • 1
  • 22
  • 38
  • 2
    One thing to be aware of when using `notifyDataSetChanged()` is that it will not animate altered items by default, whereas `notifyItemChanged()` will. – Krøllebølle Oct 31 '15 at 11:19
  • 1
    I had same error when I tried to insert items in adapter and use ```notifyItemRangeInserted(0, newNotifications.size());``` on an empty adapter. I solved this issue by checking if adapter is empty then use ```notifydatasetchanged```, else use ```notifyItemRangeInserted(0, newNotifications.size());``` – Rafael Nov 09 '15 at 06:35
  • 7
    notifyDataSetChanged will also animate items if you do 2 things 1) call setHasStableIds(true) on your Adapter and 2)override getItemid to return a unique long value from your Adapter for each row, once you do this, it ll trigger the animations – PirateApp Nov 18 '15 at 10:43
10

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

drakeet
  • 2,685
  • 1
  • 23
  • 30
8

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.

Dorukhan Arslan
  • 2,676
  • 2
  • 24
  • 42
8

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

Udit Kapahi
  • 2,277
  • 1
  • 27
  • 25
7

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.

Peter
  • 10,492
  • 21
  • 82
  • 132
Sandip Savaliya
  • 784
  • 6
  • 19
4

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.

Hiren Patel
  • 52,124
  • 21
  • 173
  • 151
4

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

3

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

Phill Wiggins
  • 2,577
  • 4
  • 28
  • 33
2

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();
}  
Community
  • 1
  • 1
miguel_rdp
  • 17
  • 3
2

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);
        }
    }
Sabodh
  • 59
  • 5
2

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;
    }
}
2

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.

Sparsh
  • 247
  • 1
  • 12
2

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.

2

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());
}
Bolling
  • 3,954
  • 1
  • 27
  • 29
1

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.

Dharmendra
  • 33,296
  • 22
  • 86
  • 129
1

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.

mainam
  • 81
  • 1
  • 2
0

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?

nunoh123
  • 1,087
  • 1
  • 10
  • 17
  • No, it just changes background of the view. – jimmy0251 Nov 09 '14 at 13:00
  • I know what it does u.u what i asked was if you were updating mSelectedArray as well when you changed the size of the items on the adapter.. the arrayindex out of bounds might be coming out of it – nunoh123 Nov 09 '14 at 18:38
  • Here's code of method : private static void setSelected(View view, boolean isSelected) { if (view != null) view.setSelected(isSelected); } – jimmy0251 Nov 09 '14 at 19:01
  • Again, I know what it does... =>>>> what i asked was if you were updating mSelectedArray as well when you changed the size of the items on the adapter.. the arrayindex out of bounds might be coming out of it – nunoh123 Nov 09 '14 at 19:05
  • No, I'm not updating mSelectedArray. – jimmy0251 Nov 09 '14 at 19:08
  • if you are changing the size of the items in the adapter you should change it there also – nunoh123 Nov 09 '14 at 19:10
  • SparseBooleanArray returns false if it doesn't contain a position, it won't throw an Exception. – jimmy0251 Nov 09 '14 at 19:20
0

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);
    }
}
yahya
  • 4,810
  • 3
  • 41
  • 58
0

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; 

wl2112
  • 1
0

In my case it solved by changing mRecyclerView.smoothScrollToPosition(0) to

mRecyclerView.scrollToPosition(0)
Krishna Meena
  • 5,693
  • 5
  • 32
  • 44
0

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

Umanda
  • 4,737
  • 3
  • 23
  • 28
0

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

}
Moaz H
  • 776
  • 1
  • 6
  • 5
0

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

dhesse
  • 3,610
  • 2
  • 20
  • 12
0

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);
    //
    //
}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
0

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")))
blinkmacalahan
  • 499
  • 5
  • 18