KarolDepka
KarolDepka

Reputation: 8628

RecyclerView: Inconsistency detected. Invalid item position

Our QA has detected a bug: when rotating the Android device (Droid Turbo), the following RecyclerView-related crash happened:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3

To me, it looks like an internal error inside RecyclerView, as I can't think of any way of this being caused directly by our code...

Has anyone encountered this problem?

What would be the solution?

A brutal workaround could be perhaps to catch the exception when it happens and re-create the RecyclverView instance from scratch, to avoid getting left with a corrupted state.

But, if possible, I would like to understand the problem better (and perhaps fix it at its source), instead of masking it.

The bug is not easy to reproduce, but it is fatal when it happens.

The full stack-trace:

W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40)
    E/AndroidRuntime( 7546): FATAL EXCEPTION: main
    E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546
    E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
    E/AndroidRuntime( 7546):    at andro

Upvotes: 353

Views: 190153

Answers (30)

Sergio
Sergio

Reputation: 30715

I had this error when DiffUtil.calculateDiff() method was used incorrectly. In my case wrong list was passed to MyCallback(). To make sure it works correctly follow the code:

val oldList = adapter.getData() // get old list
val result = DiffUtil.calculateDiff(MyCallback(oldList, newList)) // calculate difference
adapter.setData(newList); // set new list to adapter
result.dispatchUpdatesTo(adapter); // dispatch updates immediately

class MyCallback(
    private val oldList: List<...>,
    private val newList: List<...>
): DiffUtil.Callback() {
    ...
}

Upvotes: 1

Rabindra Khadka
Rabindra Khadka

Reputation: 1792

On my case this approach helps to solve the problem .

val currentSize = mList.size
mList.clear()
notifyItemRangeRemoved(0, currentSize)

mList.addAll(newData)
mAdapter.notifyItemInserted(mList.size - 1)

Upvotes: 0

oguzhan
oguzhan

Reputation: 2179

If you are using setHasStableIds(true) you must be sure all dataset items has different ids, otherwise that error may be occur.

For example:

//This is our mock dataset which has FooItem type items
val dataset = listOf<FooItem>(items)

class FooItem {

    //here your variables

    private var id : Long = 0L

    //you can handle different ids at high accuracy like;
    fun getItemId() : Long {
        if (id == 0L) {
            id = abs(Random.nextLong(2, Long.MAX_VALUE))
        }
        return id
    }

}


//Then go back your adapter and set adapter item id below like
override fun getItemId(position: Int) = dataset[position].getItemId()

With this way, you will no need to close recyclerview animations and notify all dataset.

Upvotes: 0

shubham singh
shubham singh

Reputation: 119

i also had same problem since i was using different layout when dataset was empty:-

while adding first item try

adapter.notifyDataSetChanged()

then continue with

adapter.notifyItemChangedAtPosition(position)

Upvotes: 0

汪枭杰
汪枭杰

Reputation: 11

In my case, I'm using DiffUtil.calculateDiff for recycler view items, which has a race condition while updating items. I calculate data diff in background threads pool and update it in UI thread. If 2 data come, and the first data is set to adaptor between the timing that the second diff is calculated and set, the crash happens.

For example, say we have original item list A, new data B, and new data C:

  1. B-diff = calculated (A, B)
  2. C-diff = calculated (A, C)
  3. set(B-diff)
  4. set(C-diff)

The C-diff is actually incorrect since A has been updated, so if the items count decreases, recyclerview would animate on invalid positions. Not just crashes, the items may not be updated at all (or unncessary update) due to incorrect diff index.

Changing to AsyncListDiffer resolved the issue for me since it drops the first data if the second data comes too early.

reference (it describes the issue but gives another solution (updating data in order)): https://jonfhancock.com/get-threading-right-with-diffutil-423378e126d2

Upvotes: 0

Ramkesh Yadav
Ramkesh Yadav

Reputation: 1087

This is only solution which worked for me even trying many from above solutions.

1.) Intilization

CustomAdapter scrollStockAdapter = 
         new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);

2.) Write this method in adapter

public void updateList(List<StockListModel> list) {
    stockListModels.clear();
    stockListModels.addAll(list);
    notifyDataSetChanged();
}

stockListModels -> this list is which you are using in adapter .

Upvotes: 5

Sailesh Limbu
Sailesh Limbu

Reputation: 71

One possible reason for this crash.

Reloading data while recyclerview is still scrolling.

So, try recyclerview.stopScroll() before reloading your data again.

Upvotes: 2

Tom Shaw
Tom Shaw

Reputation: 139

If you have more than one recycler views sharing the same adapter, then this error pops-up so to avoid this error create separate adapters for those recycler views. This makes the recycler view to have unshared pools which stops the adapters to populate them when not required.

Upvotes: 0

Reza
Reza

Reputation: 924

I had the same issue with RecyclerView So I just notified the adapter about data set change right after the list cleared.

mList.clear();
mAdapter.notifyDataSetChanged();

mList.addAll(newData);
mAdapter.notifyDataSetChanged();

Upvotes: 27

Andrij
Andrij

Reputation: 228

Updated RecyclerView to newest 1.2.1 version. Aaand Magic happend! Couldn't break my app anymore :) So.. try to replace old version number with the newest one in your gradle file

dependencies {
    ...
    // RecyclerView
    def recyclerview_version = "1.2.1"
    implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
}

Upvotes: 0

Shams
Shams

Reputation: 398

You can reproduce this crash in the following way

  1. Clear adapter items list.clear(). (Do not call notify*** methods)
  2. Scroll recycler view.

So I assume this crash happens when you remove items from the list and scroll without calling notify methods

Upvotes: 3

emad pirayesh
emad pirayesh

Reputation: 694

If notifyItemChanged(int position), notifyItemInserted(int position) Use instead of each other This problem occurs when you want to update the item use notifyItemChanged and when you want to add the item use notifyItemInserted

Upvotes: 0

Yamashiro Rion
Yamashiro Rion

Reputation: 1976

I've just fixed the same issue. I had a RecyclerView.Adapter with setHasStableIds(true) set to avoid items blinking.

I was using a duplicatable field in getItemId() (my model has no id field):

override fun getItemId(position: Int): Long {
    // Error-prone due to possibly duplicate name.
    return contacts[position].name.hashCode().toLong()
}

getItemId() should return a unique id for each item, so the solution was to do it:

override fun getItemId(position: Int): Long {
    // Contact's phone is unique, so I use it instead.
    return contacts[position].phone.hashCode().toLong()
}

Upvotes: 3

CHARFEDDINE Amine
CHARFEDDINE Amine

Reputation: 141

If anyone is having the same issue, I overrided these two methods in my adapter, it worked perfectly.

        override fun getItemId(position: Int): Long {
            return position.toLong()
        }
    
        override fun getItemViewType(position: Int): Int {
            return position
        }

Upvotes: -2

Rickon Gao
Rickon Gao

Reputation: 146

extends LinearLayoutManager and catch this error

public class NoCrashLinearLayoutManager extends LinearLayoutManager {

    public NoCrashLinearLayoutManager(Context context) {
        super(context);
    }

    public NoCrashLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public NoCrashLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e){
            e.printStackTrace();
        }
    }
}

Upvotes: 5

abdu
abdu

Reputation: 667

Most of the answers are disabling animations or creating new copies of data which is an overkill in my opinion. They do work but they don't address the root cause of the issue. In my case it was due to using DiffUtils in a wrong way. In the areItemsTheSame(old, new) method I did not properly implement the equality check, that, in turn cased the adapter to be notified by notifyItemRangeInserted() even though the same number of items are in the new list. So pay close attention to how you implement the DiffUtils callbacks!

Upvotes: 0

CoolMind
CoolMind

Reputation: 28845

This exception raised on API 19, 21 (but not new). In Kotlin coroutine I loaded data (in background thread) and in UI thread added and showed them:

adapter.addItem(item)

Adapter:

var list: MutableList<Item> = mutableListOf()

init {
    this.setHasStableIds(true)
}

open fun addItem(item: Item) {
    list.add(item)
    notifyItemInserted(list.lastIndex)
}

For some reason Android doesn't render quick enough or something else, so, I update a list in post method of the RecyclerView (add, remove, update events of items):

view.recycler_view.post { adapter.addItem(item) }

This exception is similar to "Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure & layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.": Recyclerview - cannot call this method in a scroll callback.

Upvotes: 3

Sergio
Sergio

Reputation: 30715

Using ListAdapter (androidx.recyclerview.widget.ListAdapter) call adapter.submitList(null) before calling adapter.submitList(list):

adapter.submitList(null)
adapter.submitList(someDataList)

Upvotes: 3

Shailendra Madda
Shailendra Madda

Reputation: 21551

In my case I got this exception

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder

None of the answers above are worked in my case. Because I was updating/changing an existing item in the adapter but I used

myAdapter.notifyItemInserted(position)

Instead, I should use this

myAdapter.notifyItemChanged(position)

Note: We should use notifyItemInserted() when we insert an item and we should use notifyItemChanged() when we update an item in the adapter.

Upvotes: 0

hamil.Dev
hamil.Dev

Reputation: 398

in my case , this solve my issue

rv.setAdapter(null);
rv.setItemAnimator(null);

PS: my problem ocur when i do seache filter in my recyclerview adapter

Upvotes: -1

OzzyTheGiant
OzzyTheGiant

Reputation: 739

One way I managed to fix this (in a Kotlin app with architecture components) was by setting if (recyclerView.adapter == null) recyclerView.adapter = MyAdapter(datasource) after I fetched the data from the Repository. Apparently it might have something to do with async issues with the suspend functions in the repository because when I fetch data for the first time when starting the activity, it calls the REST API since there is no data in the db, and everything goes smoothly, but after that, that same query cannot be made again, causing a crash.

Upvotes: 0

Ucdemir
Ucdemir

Reputation: 3098

The problem occured on me when I had not recoginized I did call two times with different threads simultaneously .

notifyDataSetChanged

one from sqlite load function one from after calling function

Upvotes: 0

just aguy
just aguy

Reputation: 47

Very late response, but this may help someone on the feature.

Make sure that your onStop or onPause methods aren't clearing any of your lists

Upvotes: 0

Kas Hunt
Kas Hunt

Reputation: 3188

I had a (possibly) related issue - entering a new instance of an activity with a RecyclerView, but with a smaller adapter was triggering this crash for me.

RecyclerView.dispatchLayout() can try to pull items from the scrap before calling mRecycler.clearOldPositions(). The consequence being is that it was pulling items from the common pool that had positions higher than the adapter size.

Fortunately, it only does this if PredictiveAnimations are enabled, so my solution was to subclass GridLayoutManager (LinearLayoutManager has the same problem and 'fix'), and override supportsPredictiveItemAnimations() to return false :

/**
 * No Predictive Animations GridLayoutManager
 */
private static class NpaGridLayoutManager extends GridLayoutManager {
    /**
     * Disable predictive animations. There is a bug in RecyclerView which causes views that
     * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
     * adapter size has decreased since the ViewHolder was recycled.
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }

    public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public NpaGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }
}

Upvotes: 272

Ramesh Pokharel
Ramesh Pokharel

Reputation: 451

This error occur when the list in adapter clear when user scrolling which make position of item holder changing, lost ref between list and item on ui, error happen in next "notifyDataSetChanged" request.

Fix:

Review your update list method. If you do something like

mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();

===> Error occur

How to fix. Create new list object for buffer processing and assign again to main list after that

List res = new ArrayList();
…..
res.add();  //add item or modify list
….
mainList = res;
notifyDataSetChanged();

Thanks to Nhan Cao for this great help :)

Upvotes: 22

Fatih Gee
Fatih Gee

Reputation: 236

For me, it worked after adding this line of code:

mRecyclerView.setItemAnimator(null);

Upvotes: 9

Steve
Steve

Reputation: 561

I ran into this nasty stack trace with the new Android Architecture Components recently. Essentially, I have a list of items in my ViewModel that are observed by my Fragment, using LiveData. When the ViewModel posts a new value for the data, the Fragment updates the adapter, passing in these new data elements and notifying the adapter that there have been changes.

Unfortunately, when passing in the new data elements to the adapter, I failed to account for the fact that both the ViewModel and the Adapter would be pointing to the same object reference! Meaning that if I update the data and call postValue() from within the ViewModel, there's a very small window where the data could be updated and the adapter not yet notified!

My fix was to instantiate a fresh copy of the elements when passed in to the adapter:

mList = new ArrayList<>(passedList);

With this super easy fix you can be ensured your adapter data will not change until right before your adapter is notified.

Upvotes: 3

Irfan Ullah
Irfan Ullah

Reputation: 69

Sorry For late but perfect solution: when you try to remove a specific item just call notifydatasetchange() and get these item in bindviewholder and remove that item and add again to the last of list and then chek list position if this is last index then remove item. basically the problem is come when you try to remove item from the center. if you remove item from last index then there have no more recycling and also your adpter count are mantine (this is critical point crash come here) and the crash is solved the code snippet below .

 holder.itemLayout.setVisibility( View.GONE );//to hide temprory it show like you have removed item

        Model current = list.get( position );
        list.remove( current );
        list.add( list.size(), current );//add agine to last index
        if(position==list.size()-1){// remove from last index
             list.remove( position );
        }

Upvotes: 0

Manish Sharma
Manish Sharma

Reputation: 1

add_location.removeAllViews();

            for (int i=0;i<arrayList.size();i++)
            {
                add_location.addView(new HolderDropoff(AddDropOffActivtity.this,add_location,arrayList,AddDropOffActivtity.this,this));
            }
            add_location.getAdapter().notifyDataSetChanged();

Upvotes: 0

In my case I was trying to change my adapter contents on a background thread but called notify* on the main/ui thread.

That is not possible! The reason why notify is forced to main thread is that the recyclerview wants you to edit your backing adapter on the main thread, even on the same call stack.

To solve the problem make sure that every operation to your adapter as well as every notify... call is made on the ui/main thread!

Upvotes: 2

Related Questions