RecyclerView: Inconsistency detected. Invalid item position

﹥>﹥吖頭↗ 提交于 2019-11-26 11:31:20
Kas Hunt

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

In my case (delete/insert data in my data structure) I needed to clear recycle pool and then notify data set changed!

mRecyclerView.getRecycledViewPool().clear(); mAdapter.notifyDataSetChanged();

Use notifyDataSetChanged() instead notifyItem... in this case.

I solved this by delaying the mRecycler.setAdapter(itemsAdapter) till after adding all the items to the adapter with mRecycler.addAll(items) and it worked. No idea why i did that to begin with, it was from a library's code that I looked over and saw those lines in the "wrong order", I'm pretty sure this is it though, please if someone can confirm it explain why it's so? Not sure if this is a valid answer even

I had a similar problem but not exactly the same. In my case at 1 point I was clearing the array that was passed to the recyclerview

mObjects.clear();

and not calling notifyDataSetChanged, as I did not want the recyclerview to immediately clear the views. I was re-filling the mObjects array in AsyncTask.

I have same issue .It was occur when I was scrolling fast and calling API and updating data.After trying all things to prevent crash , I found solution.

mRecyclerView.stopScroll();

It will work.

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

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

My problem went away after I modified my Adapter implementation to use a copy of the items array instead of a reference. The setItems() method is called each time we have new items to show in the RecyclerView.

Instead of:

private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
     private List<MyItem> mItems;  

    (....)

    void setItems(List<MyItem> items) {
        mItems = items;
    }
}

I did:

void setItems(List<MyItem> items) {
    mItems = new ArrayList<>(items);
}

Use

notifyDataSetChanged()

instead

notifyItemRangeInserted(0, YourArrayList.size())

in this case.

EKN

For fix this issue just call notifyDataSetChanged() with empty list before updating recycle view.

For example

//Method for refresh recycle view

    if (!hcpArray.isEmpty())

hcpArray.clear();//The list for update recycle view

adapter.notifyDataSetChanged();
Erhannis

In my case, I was updating the items and calling notifyDataSetChanged in a non-UI thread. Most of the time it worked, but when a lot of changes happened quickly, it'd crash. When I did, instead, basically

activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        changeData();
        notifyDataSetChanged();
    }
});

then it stopped crashing.

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

You only need to clear your list on OnPostExecute() and not while doing Pull to Refresh

// Setup refresh listener which triggers new data loading
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {

                AsyncTask<String,Void,String> task = new get_listings();
                task.execute(); // clear listing inside onPostExecute

            }
        });

I discovered that this happens when you scroll during a pull to refresh, since I was clearing the list before the async task , resulting to java.lang.IndexOutOfBoundsException: Inconsistency detected.

        swipeContainer.setRefreshing(false);
        //TODO : This is very crucial , You need to clear before populating new items 
        listings.clear();

That way you won't end with an inconsistency

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.

this problem may happen when you try clearing your list, if you are going to clear your data list especially when you are using pull to refresh 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() ; 
 }

}

It also can be related with setting the adapter multiple times at the same time. I had a callback method which was triggered 5-6 times at the same time and I was setting the adapter in that callback so RecycledViewPool couldn't handle with all of those datas contemporaneously. It's a fat chance but you better check it out anyway.

I've faced with the same situation. And it was solved by adding codes before you clear your collection.

mRecyclerView.getRecycledViewPool().clear();

I ran into a similar issue and just figured it out. I hard-coded a few examples for a test case but didn't ensure they each returned a unique ID and that caused the below crash for me. Fixing the IDs resolved the issue, hope this helps someone else!

darkchaos

In my case I just removed line with setHasStableIds(true);

Just remove all views of your layout Manager before notify. like:

myLayoutmanager.removeAllViews();

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!

I solved the issue, adding items one by one when it gets new data. I use this function inside adapter.

public void add(Data item) {
        if(!params.contains(item){
           params.add(item);
           notifyItemInserted(getItemCount() - 1);
        }
    }
}

I found that setting mRecycler.setLayoutFrozen(true); in the onRefresh method of the swipeContainer.

solved the problem for me.

swipeContainer.setOnRefreshListener(new   SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            orderlistRecycler.setLayoutFrozen(true);
            loadData(false);

        }
    });

I had a same issue previously. Finally found a workaround for that

What i do is to notify adapter that item has removed and then notify adapter data set range changed

 public void setData(List<Data> dataList) {
      if (this.dataList.size() > 0) {
          notifyItemRangeRemoved(0, dataList.size());
          this.dataList.clear();
      }
      this.dataList.addAll(dataList)
      notifyItemRangeChanged(0, dataList.size());

 }
DroidBender

This is quite a nasty bug.

To handle my item click, I used an implementation of the RecyclerView.OnItemTouchListener similar to the solution found in this question.

After many times of refreshing the RecyclerView's datasource and clicking an item, this IndexOutOfBoundsException would crash my application. When an item is clicked, the RecyclerView internally goes looking for the correct underlying view and gives back it position. Checking out the source code, I saw that there were some Tasks and Threads scheduled. To cut the story short, basically it's just some illegal state where two datasources are intermixed and not synchronized and the whole thing goes wild.

Based on this, I removed my implementation of the RecyclerView.OnItemTouchListener and simply caught the click on the ViewHolder of the Adapter myself:

public void onBindViewHolder (final BaseContentView holder, final int position) {

    holder.itemView.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick (View view) {

        // do whatever you like here
      }
    });

}

This might not be the best solution, but a crash-free on for now.. Hopefully this will save you some time :).

i once got the error too:

Cause: I was trying to update a Recycler View from an Async task while simultaneously trying to get old deleted viewHolders;

Code: I generate data at the press of a button, logic as follows

  1. Clear the last items in the recycler view
  2. Call async task to generate data
  3. OnPostExecute Update the Recycler view and NotifyDataSetChanged

Problem: Whenever i scroll fast before generating my data i get

Inconsistency detected. Invalid view holder adapter positionViewHolder java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 20(offset:2).state:3

Solution: instead of clearing the RecyclerView before generating my data, i instead leave it and then Replace it with the New Data, the Call NotifyDatasetChanged, as shown below;

       @Override
        protected void onPostExecute(List<Objects> o) {
            super.onPostExecute(o);
            recyclerViewAdapter.setList(o);
            mProgressBar.setVisibility(View.GONE);
            mRecyclerView.setVisibility(View.VISIBLE);
        }

Lint gave me an advice concerning inconsistency: I wrote (onBindViewHolder()):

pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        doStuff(position);
                    }
                });

which had to be replaced by :

pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        doStuff(pholder.getAdapterPosition());
                    }
                });

Run both codes in your code and then run Lint for the full explaination!!

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

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 );
        }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!