1

I'm building an infinite horizontal scroll list by using a Recycler view. 1-2 children can be visible on the screen at all times (since their width is more than the screen viewport and so we'll never have more than 2 children visible).

Initial state:

initial state


While the user is scrolling (they can scroll BOTH forward and backwards - but for the shake of this example, let's assume they are scrolling BACKWARDS), I'm fetching more data, and I'm prepending them to my adapter data.

I'm then calling notifyItemRangeInserted - so far so good.

After prepend:

After prepend and notifyItemRangeInserted


Clipping time:

clipping the opposite end of data

At the same time I add all those children I check wether we now end up with more than MAX_DATASOURCE_CNT items in the datasource.

Let's assume that MAX_DATASOURCE_CNT is 6 for this example.

If we do, I remove the unnecessary extra children from the end of our data source and then call notifyItemRangeRemoved.

I do that because I don't want to flood the device with unused data. I'm over simplifying the example now, but in fact my view can accumulate thousands of unused data items as the user is scrolling, and I want to trim the opposite end of my datasource on the fly.

The problem:

devil

After the prepend process takes place, if the user scrolls too fast, sometimes onBindViewHolder(RecyclerView.ViewHolder holder, int position) get's called with position == 0 (also holder.getAdapterPosition() == 0 )

That totally messes up my view as it is as if my view jumped to a totally irrelevant child and shows that one.

Current workaround:

I noticed that if I remove the whole clipping process which is basically these lines of code:

if (itemCountAfterPrepend > MAX_DATASOURCE_CNT) {

    int extraItemCnt = itemCountAfterPrepend - MAX_DATASOURCE_CNT;

    // remove every extra item from the end of the array
    removeRange(MAX_DATASOURCE_CNT, itemCountAfterPrepend);

    // and notify our adapter about the removal
    notifyItemRangeRemoved(MAX_DATASOURCE_CNT, extraItemCnt);
}

everything works fine. But I really want to trim my data. What can I do?

Thank you

SudoPlz
  • 20,996
  • 12
  • 82
  • 123
  • Does it help to increase the value of `MAX_DATASOURCE_CNT`? – Jantzilla Jun 01 '18 at 17:13
  • Not really. Why would that help? – SudoPlz Jun 01 '18 at 17:35
  • Isn't it the basis for determining amount of clipping – Jantzilla Jun 01 '18 at 17:37
  • Yes, but the whole point is, I want to clip, I don't want it to trigger less often. Besides, making it trigger less often doesn't resolve the problem. – SudoPlz Jun 01 '18 at 17:41
  • 1
    I don't understand why do you load more data than enough. You need to implement pagination and you don't have to remove rows. – just Jun 01 '18 at 17:48
  • This infinite scroll horizontal recyclerview represents a calendar and each item is a day (that contains appointments etc), and there will never be a point when I have enough data. The user can go as far behind as they wish to, and they can travel as far forward as they want as well. But I can't keep all the calendar days in memory, that's not smart. Does it make more sense now? – SudoPlz Jun 01 '18 at 17:51
  • I guess what I mean is adjust your code to clip less so that scroll quickly doesn't remove the data integrity – Jantzilla Jun 01 '18 at 17:52
  • Instead of give the all new fetched list, may be you should give the diff between the MAX_DATASOURCE_CNT and the adapter item length? – Cochi Jun 01 '18 at 17:58
  • @Cochi I'm adding children to the front of the list, and removing them from the end of the list. What you suggest would work if I was adding AND removing from the same side. – SudoPlz Jun 01 '18 at 18:01

1 Answers1

0

Ok I didn't find a concrete answer, so instead here's what I did.

Since the issue was notifyItemRangeRemoved running while a view is being bound, and a view was been bound because the user was still scrolling, I removed the clipping process from the prepend/append functions.

Instead I made the clipping process a standalone function (clipExtraDataIfNecessary).

This ONLY get's called in onScrollStateChanged(RecyclerView recyclerView, int newState) and ONLY when newState == SCROLL_STATE_IDLE, i.e the user has stopped scrolling:

The data clipping methods look like this now:

/**
 * make sure we never surpass the MAX_DATASOURCE_CNT limit
 * Clips the data ArrayList from the ending (right) side
 */
public void clipExtraEndData() {
    int itemCountAfterPrepend = this.data.size();
    // if we went above our max data source count limit
    if (itemCountAfterPrepend > MAX_DATASOURCE_CNT) {
        int extraItemCnt = itemCountAfterPrepend - MAX_DATASOURCE_CNT;

        synchronized (dataMutationLock) {
            // remove every extra item from the end of the array
            removeRange(MAX_DATASOURCE_CNT, itemCountAfterPrepend);

            // and notify our adapter about the removal
            notifyItemRangeRemoved(MAX_DATASOURCE_CNT, extraItemCnt);
        }
    }
}


/**
 * make sure we never surpass the MAX_DATASOURCE_CNT limit
 * Clips the data ArrayList from the beginning (left) side
 */
public void clipExtraStartData() {
    int itemCountAfterAppend = this.data.size();
    // if we went above our max data-source count limit
    if (itemCountAfterAppend > MAX_DATASOURCE_CNT) {
        int extraItemCnt = itemCountAfterAppend - MAX_DATASOURCE_CNT;

        synchronized (dataMutationLock) {
            // remove every extra item from the beginning of the array
            removeRange(0, extraItemCnt);

            // and notify our adapter about the removal
            notifyItemRangeRemoved(0, extraItemCnt);
        }
    }
}

/**
 * Removes all the extra data that we have accumulated by prepending/appending
 * as the user was scrolling
 *
 * CAUTION: MAKE SURE YOU RUN THIS at a time where the user is NOT scrolling,
 * otherwise we run the risk of bindViewRow being called with preLayout position
 * values (a.k.a 0)
 * @param curVisiblePosition
 */
public void clipExtraDataIfNecessary(int curVisiblePosition) {
    int initialItemCnt = size();
    if (initialItemCnt > MAX_DATASOURCE_CNT) {
        if (curVisiblePosition < initialItemCnt - curVisiblePosition) {
            // we have extra children on the end of our data
            clipExtraEndData();
        } else {
            // we have extra children on the beginning of our data
            clipExtraStartData();
        }
    }
}
SudoPlz
  • 20,996
  • 12
  • 82
  • 123