Recycler View Inconsistency Detected error, coming while scrolling fast or scrolling while loading more items..
FATAL EXCEPTION: main Process: com.pratap.endlessrecyclerview, PID: 21997 java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{56a082c position=40 id=-1, oldPos=39, pLpos:39 scrap [attachedScrap] tmpDetached no parent} at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4251) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4382) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4363) at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1961) at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1370) at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1333) at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:562) at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2864) at android.support.v7.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1445) at android.support.v7.widget.RecyclerView.access$400(RecyclerView.java:144) at android.support.v7.widget.RecyclerView$1.run(RecyclerView.java:282) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858) at android.view.Choreographer.doCallbacks(Choreographer.java:670) at android.view.Choreographer.doFrame(Choreographer.java:603) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844) at android.os.Handler.handleCallback(Handler.java:746) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5443) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
Adapter
public class DataAdapter extends RecyclerView.Adapter { private final int VIEW_ITEM = 1; private final int VIEW_PROG = 0; private List<Feed> mFeed; // The minimum amount of items to have below your current scroll position // before loading more. private int visibleThreshold = 5; private int lastVisibleItem, totalItemCount; private boolean loading; private OnLoadMoreListener onLoadMoreListener; public DataAdapter(List<Feed> feeds, RecyclerView recyclerView) { mFeed = feeds; if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) { final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView .getLayoutManager(); recyclerView .addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = linearLayoutManager.getItemCount(); lastVisibleItem = linearLayoutManager .findLastVisibleItemPosition(); if (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) { // End has been reached // Do something if (onLoadMoreListener != null) { onLoadMoreListener.onLoadMore(); } loading = true; } } }); } } @Override public int getItemViewType(int position) { return mFeed.get(position) == null ? VIEW_PROG : VIEW_ITEM; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder vh; if (viewType == VIEW_ITEM) { View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.list_row, parent, false); vh = new StudentViewHolder(v); } else { View v = LayoutInflater.from(parent.getContext()).inflate( R.layout.progress_item, parent, false); vh = new ProgressViewHolder(v); } return vh; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof StudentViewHolder) { Feed singleStudent= (Feed) mFeed.get(position); ((StudentViewHolder) holder).tvName.setText(singleStudent.getTitle()); ((StudentViewHolder) holder).student= singleStudent; } else { ProgressViewHolder.PROGRESS_BAR.setIndeterminate(true); } } public void setLoaded() { loading = false; } public void addFeed(Feed feed) { mFeed.add(feed); //mFeed.addAll(0, (Collection<? extends Feed>) feed); notifyItemInserted(mFeed.size()); //notifyItemRangeInserted(0,mFeed.size()); notifyDataSetChanged(); //notifyItemInserted(mFeed.size()); //setLoaded(); //notifyItemInserted(mFeed.size()); } public void removeAll(){ mFeed.clear(); notifyDataSetChanged(); } @Override public int getItemCount() { return mFeed.size(); } public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) { this.onLoadMoreListener = onLoadMoreListener; } public static class StudentViewHolder extends RecyclerView.ViewHolder { public TextView tvName; public Feed student; public StudentViewHolder(View v) { super(v); tvName = (TextView) v.findViewById(R.id.tvName); //tvEmailId = (TextView) v.findViewById(R.id.tvEmailId); } } public static class ProgressViewHolder extends RecyclerView.ViewHolder { //public ProgressBar progressBar; public static ProgressBar PROGRESS_BAR; public ProgressViewHolder(View v) { super(v); PROGRESS_BAR = (ProgressBar) v.findViewById(R.id.progressBar1); // progressBar = (ProgressBar) v.findViewById(R.id.progressBar1); } } }
Activity
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener { private Toolbar toolbar; private TextView tvEmptyView; private RecyclerView mRecyclerView; private DataAdapter mAdapter; private LinearLayoutManager mLayoutManager; private RestManager mManager; private List<Feed> mFeed; SwipeRefreshLayout mSwipeRefreshLayout; protected Handler handler; private int currentPage=1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); tvEmptyView = (TextView) findViewById(R.id.empty_view); mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); mSwipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); mSwipeRefreshLayout.setOnRefreshListener(this); //studentList = new ArrayList<Student>(); mFeed = new ArrayList<Feed>(); handler = new Handler(); if (toolbar != null) { setSupportActionBar(toolbar); getSupportActionBar().setTitle("Android Students"); } mManager = new RestManager(); // use this setting to improve performance if you know that changes // in content do not change the layout size of the RecyclerView mRecyclerView.setHasFixedSize(true); mLayoutManager = new LinearLayoutManager(this); // use a linear layout manager mRecyclerView.setLayoutManager(mLayoutManager); // create an Object for Adapter mAdapter = new DataAdapter(mFeed,mRecyclerView); // set the adapter object to the Recyclerview mRecyclerView.setAdapter(mAdapter); // mAdapter.notifyDataSetChanged(); loadData(false); // if (mFeed.isEmpty()) { // mRecyclerView.setVisibility(View.GONE); // tvEmptyView.setVisibility(View.VISIBLE); // // } else { // mRecyclerView.setVisibility(View.VISIBLE); // tvEmptyView.setVisibility(View.GONE); // } mAdapter.setOnLoadMoreListener(new OnLoadMoreListener() { @Override public void onLoadMore() { //add null , so the adapter will check view_type and show progress bar at bottom mFeed.add(null); mAdapter.notifyItemInserted(mFeed.size() - 1); handler.postDelayed(new Runnable() { @Override public void run() { // remove progress item mFeed.remove(mFeed.size() - 1); // mAdapter.notifyItemRemoved(mFeed.size()); //add items one by one int start = mFeed.size(); currentPage++; Log.d("CurrentPage", String.valueOf(currentPage)); Call<Results> listCall = mManager.getFeedApi().getAllFeeds(1); listCall.enqueue(new Callback<Results>() { @Override public void onResponse(Call<Results> call, Response<Results> response) { mSwipeRefreshLayout.setRefreshing(false); if (response.isSuccess()) { if (response.body() != null) { Results feedList = response.body(); // List<Results> newUsers = response.body(); Log.d("Retrofut", String.valueOf(feedList)); for (int i = 0; i < feedList.results.size(); i++) { Feed feed = feedList.results.get(i); // mFeed.add(feed); mAdapter.addFeed(feed); // mAdapter.notifyDataSetChanged(); //mAdapter.notifyItemInserted(mFeed.size()); } // mAdapter.notifyDataSetChanged(); } } } @Override public void onFailure(Call<Results> call, Throwable t) { Log.d("Retrofut", "Error"); mFeed.remove(mFeed.size() - 1); mAdapter.notifyItemRemoved(mFeed.size()); mAdapter.setLoaded(); mSwipeRefreshLayout.setRefreshing(false); } }); // for (int i = 1; i <= 20; i++) { // studentList.add(new Student("Student " + i, "androidstudent" + i + "@gmail.com")); // // } mAdapter.setLoaded(); //or you can add all at once but do not forget to call mAdapter.notifyDataSetChanged(); } }, 2000); } }); } // load initial data private void loadData(final boolean removePreData) { Call<Results> listCall = mManager.getFeedApi().getAllFeeds(1); listCall.enqueue(new Callback<Results>() { @Override public void onResponse(Call<Results> call, Response<Results> response) { if (response.isSuccess()) { if (response.body() != null) { // if(removePreData) mAdapter.removeAll(); Results feedList = response.body(); Log.d("Retrofut", String.valueOf(feedList)); for (int i = 0; i < feedList.results.size(); i++) { Feed feed = feedList.results.get(i); // mFeed.add(feed); //mAdapter.notifyDataSetChanged(); mAdapter.addFeed(feed); } mSwipeRefreshLayout.setRefreshing(false); } } } @Override public void onFailure(Call<Results> call, Throwable t) { Log.d("Retrofut", String.valueOf(t)); mFeed.remove(mFeed.size() - 1); mAdapter.notifyItemRemoved(mFeed.size()); mAdapter.setLoaded(); mSwipeRefreshLayout.setRefreshing(false); } } ); // for (int i = 1; i <= 20; i++) { // studentList.add(new Student("Student " + i, "androidstudent" + i + "@gmail.com")); // // } mSwipeRefreshLayout.setRefreshing(true); } @Override public void onRefresh() { mFeed.clear(); mAdapter.notifyDataSetChanged(); loadData(true); currentPage=1; } }
This issue is a known bug of RecyclerView. The best solution is, clear the list every time before refresh RecyclerView.
For fix this issue just call notifyDataSetChanged() with empty list before updating recycle view.
For example
//Method for refresh recycle view if (!yourList.isEmpty()) yourList.clear(); //The list for update recycle view adapter.notifyDataSetChanged();
It looks similar with known android bug
There are quite ugly, but working approach
public class WrapContentLinearLayoutManager extends LinearLayoutManager { //... constructor @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { try { super.onLayoutChildren(recycler, state); } catch (IndexOutOfBoundsException e) { Log.e("Error", "IndexOutOfBoundsException in RecyclerView happens"); } } } mRecyclerView.setLayoutManager(new WrapContentGridLayoutManager(getContext(), spanCount));
For me it works without any by-effect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With