# 使用RecyclerView遇到的一些问题 Inconsistency detected

java.lang.IndexOutOfBoundsException:
Inconsistency detected. Invalid view holder adapter positionViewHolder{424b7690 position=7 id=-1, oldPos=8,pLpos:8 scrap tmpDetached no parent} at
android.support.v7.widget.RecyclerView\$Recycler.validat
eViewHolderForOffsetPosition(RecyclerView.java:4349)


java.lang.IndexOutOfBoundsException:
Inconsistency detected. Invalid item position   157(offset:157).state:588

ISSUE 77846

## 1.ListView and RecyclerView are different

ListView and RecyclerView are different. RecyclerView is designed to work with different components and makes certain promises. For the error above, exception happens when LayoutManager tries to get a View for a position. A count is already provided to the LayoutManager at the beginning of the layout, this is a promise and guaranteed not to change until the layout is complete. RecyclerView cannot say “you have 6 items” and when LayoutManager asks for item at position 5, return null. LayoutManager might have done its own calculations based on that count, doing so may leave its state unstable. On the other hand, ListView has full control, thus it can forgive these things. Besides that, ListView does not do anything clever about the adapter contents whereas RecyclerView does a lot to support animations. The two are fairly different. To be honest, if you do not dispatch detailed notify events, there is little to no benefit on moving to RecyclerView. If you dispatch them, it will both help UX and performance. (e.g. avoiding unnecessary rebinds)

RecyclerView throws an exception because problem happens due to a developer error and should be fixed. If it is a RecyclerView, we have to fix it. In both cases, for a consistent and stable API, forgiving developer errors (both ends) is not a sustainable solution.

About adapter count, that getItemCount is one API I regret leaving public (was an old API, had to be kept for some backward compatibility).
LayoutManagers are expected to get item count from the State. If you check framework layout managers, all work w/ state. There is a strict abstraction between the Adapter and LayoutManager (due to animations). Even for notify events, RecyclerVIew re-writes them (in a consistent way) to suit them for two pass animations. There is a lot going on there, hard to explain here. (see docs: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.State.html#getItemCount())
RecyclerView also provides an API to convert layout positions to adapter positions if necessary.
convertPreLayoutPositionToPostLayout : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Recycler.html#convertPreLayoutPositionToPostLayout(int)

The trigger for the bug might be events while RV is detached, some Runnable may not be running due to View being detached. Some info / logs would be very helpful so that i can create a test case and fix it.
Thanks.

## 2.let RV know about it

Great to hear that issue is fixed. I did not understand why you are calling notifyInserted w/o inserting them. It will definitely create a problem. This explains why RV expects to have more items in the adapter.
Only call these events right after you change the data. (has to be in the same call stack ~ main looper loop~)

Notify events are handled asynchronously. So you can call as many notify events as you want and RV will handle all of them in the next layout pass.It batches them etc. You just need to guarantee that all of them are consistent with each other. That is, in every step you change adapter, you should let RV know about it. Your events should be consistent. For example, if you want to remove first two elements 1 by one, you should call:

mData.removeItemAt(0);
notifyItemRemoved(0);
mData.removeItemAt(0);
notifyItemRemoved(0);

A common mistake would be to think that you need to call notifyItemRemoved(0); then notifyItemRemoved(1);. This is NOT true as RV knows items will shift if first item is removed. This is also consistent w/ what you would do while handling a list.

This is the simplest way to get them right. Technically, you can let RV know right after you update the backing data, as long as you do it in the same call stack.
e.g.
mData.removeItemAt(0);
mData.removeItemAt(0);
notifyItemRangeRemoved(0, 2); //2 items, starting from 0.

So your code probably works fine if notifyDataSetChanged arrives before the next layout calculation but fails otherwise.

When you call notifyDataSetChanged, you void all previous notify events (in that frame). Don’t call notifyDataSetChanged if you don’t have to.

Good luck and thanks for the update, I’m closing the issue.

public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {....}

    Logger.d("state:" + state.toString());

state:State{mTargetPosition=-1, mPreLayoutHolderMap={}, mPostLayoutHolderMap={}, mData=null, mItemCount=6, mPreviousLayoutItemCount=6, mDeletedInvisibleItemCountSincePreviousLayout=0, mStructureChanged=true, mInPreLayout=false, mRunSimpleAnimations=false, mRunPredictiveAnimations=false}

01-11 2341
06-04 378