3

I have a RecyclerView with a LinearLinearManager. My method addItems() downloads data from the Internet and then adds 10 items one after the other like this:

itemIds.add(id);
listAdapter.notifyItemInserted(itemIds.size() - 1);

I call this method when the activity is started and when the user scrolls to (size - 3):

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        if (!pending && !loadingMoreItems &&
            listAdapter.getItemCount() - layoutManager.findLastVisibleItemPosition() <= 3) {
            loadingMoreItems = true;
            Log.d(LOG_TAG, "new data!");
            addItems();
        }
    }
});

When I start the application and scroll to position 7, new data is loaded. But after these items have been added (new item size is 20, I checked that), I get the following error:

java.lang.IndexOutOfBoundsException:
Inconsistency detected. Invalid view holder adapter position
ViewHolder{9cff95d position=27 id=-1, oldPos=7, pLpos:5
scrap [attachedScrap] tmpDetached no parent}

So the RecyclerView wants to reuse the ViewHolder of old position 7 for the new position 27 – but that position doesn't exist, of course.

For debugging, I added a custom class extending the LinearLayoutManager that prevents crashing because of an IndexOutOfBoundsException:

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    try {
        super.onLayoutChildren(recycler, state);
    } catch (IndexOutOfBoundsException e) {
        e.printStackTrace();
    }
}

And then my list works perfectly – new items are smoothly added to the list, even multiple times, so it results in an infinite list. There's only this little stack trace in the Logcat.

It doesn't feel right to me just ignoring this IndexOutOfBoundsException using a try/catch that actually does nothing on catch. Does anyone have an idea on how to fix that weird position 27?

Edit: After testing different item sizes, I discovered that the Recyclerview always wants to load the item at this position:

<position when reload request was started> + 2 * <reloaded item size>

Which is of course always out of the total list size.

Felix Edelmann
  • 4,959
  • 3
  • 28
  • 34
  • Could You share basic version of Your adapter? At the beginning You have 10 items, and when You reach 7th item You load more +10 items, right? – deadfish Apr 29 '17 at 20:06
  • That's true. The adapter only returns list size as itemcount and has a normal create and bind function, I don't think it's relevant for this issue. – Felix Edelmann Apr 30 '17 at 12:50

3 Answers3

3

It is happening because you are adding data to the list and at the same time you are trying to scroll the recyclerview before it's adapter is notified.

so when you are loading data after 7th position do this

mData.add(yourdata);
mAdapter.notifyDataSetChanged();

if you will keep adding the data and keep scrolling it. it will give you "Inconsistency detected." error. Because adapter thinks thare are only 7 items and when you scroll it has become 10 which is not notified to adaper.

Felix Edelmann
  • 4,959
  • 3
  • 28
  • 34
Reyansh Mishra
  • 1,879
  • 1
  • 14
  • 26
  • Replacing `notifyItemInserted(...)` with `notifyDataSetChanged()` did the trick for me! Don't know why, as `notifyItemInserted` is actually more specific and therefore better, but anyway thanks for your correct answer! (PS: `mAdapter.updateData(mData);` isn't needed in my opinion because both the adapter and my activity reference to the same List object that holds all data items.) – Felix Edelmann Apr 30 '17 at 11:16
1

Looks like you have to use this method listAdapter.notifyItemRangeInserted(startPosition, itemCount);

notifyItemInserted(position) is used to insert only one item. If you want to insert more than one items you have to use notifyItemRangeInserted(position, itemCount)

Would you use this method to insert all 10 items instead of adding one by one in a loop and check whether you are getting the same exception?

Bob
  • 13,447
  • 7
  • 35
  • 45
0

Besides what Dinesh Bob has said, I don't think there is anything wrong with your code. I am just going to post how I implement pagination in a RecyclerView:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            int visibleItemCount=linearLayoutManager.getChildCount();
            int totalItemCount=linearLayoutManager.getItemCount();
            int firstVisibleItemPosition=linearLayoutManager.findFirstVisibleItemPosition();

            if (!isPageLoading && !isLastPage) {
                if ((visibleItemCount + firstVisibleItemPosition)>=totalItemCount) {
                    offset+=limit;
                    loadMoreItems(offset);
                }
            }
        }
    });

The loadMoreItems goes ahead and makes an API call and then adds the items to the adapter like below:

public void addAllItems(List<Item> itemList) {
    for (Item itemRow: itemList) {
        this.itemList.add(itemRow);
        notifyItemInserted(this.itemList.size() - 1);
    }
}

The block to catch the exception was necessary when implementing pagination while using ListView due to a bug in it. But the recyclerview doesn't need it.

Will also give your code a shot to see what exactly is wrong with it.

Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
Bluesir9
  • 121
  • 5
  • 11
  • Thanks, your code is almost the same like mine – I switched to firstVisibleItemPosition instead of lastVisible and used linearLayoutManager.getChildCount() instead of adapter.getChildCount(), but the error also still remains. – Felix Edelmann Apr 30 '17 at 11:04