Reputation: 8628
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
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
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
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
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:
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
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
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
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
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
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
Reputation: 398
You can reproduce this crash in the following way
list.clear()
. (Do not call notify*** methods)So I assume this crash happens when you remove items from the list and scroll without calling notify methods
Upvotes: 3
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Reputation: 236
For me, it worked after adding this line of code:
mRecyclerView.setItemAnimator(null);
Upvotes: 9
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
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
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
Reputation: 398
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