Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView: Inconsistency detected. Invalid item position - why is that?

Our QA has detected a bug: the following RecyclerView-related crash happened: `

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

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 easy to reproduce scrolling continues fast creating this crash, but it is fatal when it happens.

My wokring code is:

public class MoviesGridRecyclerViewDataAdapter  extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

public static final int VIEW_ITEM = 1;
public static final int VIEW_PROG = -1;
private static final int FADE_DURATION = 500;
int pastVisiblesItems, visibleItemCount, totalItemCount;
private List<VideoEntity> dataList;
private FragmentActivity activity;
private int visibleThreshold = 1;
//private int lastVisibleItem, totalItemCount;
//private boolean loading = false;
private OnLoadMoreListener onLoadMoreListener;
private RecyclerView.OnScrollListener recyclerciewScrollListener;
private RecyclerView recyclerView;
private boolean followLargeItemOnDataRender;
private boolean loading = false;
private boolean isLiveChannel = false;

public MoviesGridRecyclerViewDataAdapter(FragmentActivity fragmentActivity, final List<VideoEntity> dataList, RecyclerView recycler, boolean followLargeItemOnOddData, boolean isLiveChannels) {
    this.dataList = dataList;
    this.activity = fragmentActivity;
    this.followLargeItemOnDataRender = followLargeItemOnOddData;
    this.recyclerView = recycler;
    isLiveChannel = isLiveChannels;

    Log.e("VideoGridRVDataAdapter", "True");

    if (this.recyclerView.getLayoutManager() instanceof GridLayoutManager) {

        final GridLayoutManager gridLayoutManager = (GridLayoutManager) this.recyclerView.getLayoutManager();
        recyclerciewScrollListener = new RecyclerView.OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                if (dy > 0) //check for scroll down
                {
                    visibleItemCount = gridLayoutManager.getChildCount();
                    totalItemCount = gridLayoutManager.getItemCount();
                    pastVisiblesItems = gridLayoutManager.findFirstVisibleItemPosition();

                    if (!loading) {
                        if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
                            loading = true;


                            if (onLoadMoreListener != null) {
                                onLoadMoreListener.onLoadMore(visibleItemCount + pastVisiblesItems);
                            }
                        }
                    }
                }
            }
        };

        this.recyclerView.addOnScrollListener(recyclerciewScrollListener);
    }
}


public void notifyDataLoadingStartProgress() {
    dataList.add(null);
    notifyItemInserted(dataList.size() - 1);
}

public void notifyDataLoaded() {
    dataList.remove(dataList.size() - 1);
    notifyItemRemoved(dataList.size());
    setLoaded();
}


public void resetItems(@NonNull List<VideoEntity> newDataSet) {
    loading = false;
    pastVisiblesItems = visibleItemCount = totalItemCount = 0;

    dataList.clear();
    addItems(newDataSet);
}

public void addItems(@NonNull List<VideoEntity> newDataSetItems) {
    dataList.addAll(newDataSetItems);
    postAndNotifyAdapter(new Handler());
}



public void addItem(VideoEntity item) {
    if (!dataList.contains(item)) {
        dataList.add(item);
        notifyItemInserted(dataList.size() - 1);
    }
}

public void removeItem(VideoEntity item) {
    int indexOfItem = dataList.indexOf(item);
    if (indexOfItem != -1) {
        this.dataList.remove(indexOfItem);
        notifyItemRemoved(indexOfItem);
    }
}

private void postAndNotifyAdapter(final Handler handler) {
    handler.post(new Runnable() {
        @Override
        public void run() {
            if (!recyclerView.isComputingLayout()) {
                try {
                    recyclerView.getRecycledViewPool().clear();
                    notifyDataSetChanged();
                } catch (IndexOutOfBoundsException ex) {
                }

            } else {
                postAndNotifyAdapter(handler);
            }
        }
    });
}

public void destroy() {
    this.recyclerView.removeOnScrollListener(recyclerciewScrollListener);
}


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    RecyclerView.ViewHolder vh;
    View v;
    if (viewType == VIEW_PROG) {
        v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_progressbar, viewGroup, false);
        vh = new ProgressViewHolder(v);

    } else {
        Integer layoutResourceId;

        if (isLiveChannel) {
            layoutResourceId = R.layout.item_video_grid_norma_hs_overfow_overdraw;

        } else {
            layoutResourceId = R.layout.item_video_linear_overdraw;

        }
        v = LayoutInflater.from(viewGroup.getContext()).inflate(layoutResourceId, viewGroup, false);
        vh = new MoviesItemRowHolder(this, dataList, v, activity);
    }
    return vh;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {

    if (holder instanceof MoviesItemRowHolder) {
        VideoEntity singleItem = dataList.get(i);
        ((MoviesItemRowHolder) holder).bind(singleItem,dataList);
        //setScaleAnimation(holder.itemView);
    } else {
        ((ProgressViewHolder) holder).progressBar.setIndeterminate(true);
    }
}

public boolean isLoading() {
    return loading;
}

public void setLoaded() {
    loading = false;
}

private void setFadeAnimation(View view) {
    AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
    anim.setDuration(FADE_DURATION);
    view.startAnimation(anim);
}

private void setScaleAnimation(View view) {
    ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    anim.setDuration(FADE_DURATION);
    view.startAnimation(anim);
}

/**
 * Return the view type of the item at <code>position</code> for the purposes
 * of view recycling.
 * <p/>
 * <p>The default implementation of this method returns 0, making the assumption of
 * a single view type for the adapter. Unlike ListView adapters, types need not
 * be contiguous. Consider using id resources to uniquely identify item view types.
 *
 * @param position position to query
 * @return integer value identifying the type of the view needed to represent the item at
 * <code>position</code>. Type codes need not be contiguous.
 */
@Override
public int getItemViewType(int position) {
    //return position;

    return dataList.get(position) != null ? position : VIEW_PROG;
}

@Override
public int getItemCount() {
    return (null != dataList ? dataList.size() : 0);
}

public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
    this.onLoadMoreListener = onLoadMoreListener;
}

public List<VideoEntity> getItems() {
    return dataList;
}
}

Please have a look my class and let me know whats going wrong, when i scrolling fast this crash happend in fewer devices.

And Stack Trace is:

Fatal Exception: java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 17(offset:17).state:18 android.support.v7.widget.RecyclerView{4266ee78 VFED.... .F....ID 0,0-480,625 #7f0902db app:id/recycler_view_list}, adapter:com.tapmad.tapmadtv.f.e@425896c0, layout:com.tapmad.tapmadtv.WrapContentLinearLayoutManager@424092b0, context:com.tapmad.tapmadtv.activities.SectionMoreActivityHS@4244c478
   at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline + 5923(RecyclerView.java:5923)
   at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition + 5858(RecyclerView.java:5858)
   at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition + 5854(RecyclerView.java:5854)
   at android.support.v7.widget.LinearLayoutManager$LayoutState.next + 2230(LinearLayoutManager.java:2230)
   at android.support.v7.widget.GridLayoutManager.layoutChunk + 557(GridLayoutManager.java:557)
   at android.support.v7.widget.LinearLayoutManager.fill + 1517(LinearLayoutManager.java:1517)
   at android.support.v7.widget.LinearLayoutManager.scrollBy + 1331(LinearLayoutManager.java:1331)
   at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy + 1075(LinearLayoutManager.java:1075)
   at android.support.v7.widget.GridLayoutManager.scrollVerticallyBy + 382(GridLayoutManager.java:382)
   at android.support.v7.widget.RecyclerView.scrollStep + 1832(RecyclerView.java:1832)
   at android.support.v7.widget.RecyclerView$ViewFlinger.run + 5067(RecyclerView.java:5067)
   at android.view.Choreographer$CallbackRecord.run + 791(Choreographer.java:791)
   at android.view.Choreographer.doCallbacks + 591(Choreographer.java:591)
   at android.view.Choreographer.doFrame + 560(Choreographer.java:560)
   at android.view.Choreographer$FrameDisplayEventReceiver.run + 777(Choreographer.java:777)
   at android.os.Handler.handleCallback + 725(Handler.java:725)
   at android.os.Handler.dispatchMessage + 92(Handler.java:92)
   at android.os.Looper.loop + 176(Looper.java:176)
   at android.app.ActivityThread.main + 5317(ActivityThread.java:5317)
   at java.lang.reflect.Method.invokeNative(Method.java)
   at java.lang.reflect.Method.invoke + 511(Method.java:511)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run + 1102(ZygoteInit.java:1102)
   at com.android.internal.os.ZygoteInit.main + 869(ZygoteInit.java:869)
   at dalvik.system.NativeStart.main(NativeStart.java)

Any lead highly appreciated Thanks.

like image 659
Jhaman Das Avatar asked Nov 06 '22 13:11

Jhaman Das


1 Answers

I found the issue in your code

The issue is that when u clear the arrayList

dataList.clear();

You must notify the adapter the dataset has been changed. else adapter is unaware of the clearing of the dataset and its calculation went wrong

dataList.clear();
notifyDataSetChanged();

Hence recyclerview adapter will know there is a clearing of data happens and it will recalculate positions. And when u add new data there won't be any inconsistency.

like image 114
Eldhopj Avatar answered Nov 12 '22 17:11

Eldhopj