DrKLO/Telegram · error · IllegalArgumentException

Layout positions must be non-negative

Error message

Layout positions must be non-negative

What it means

GapWorker prefetches views likely to scroll into view soon. A nested LayoutManager reports positions it wants prefetched via LayoutManager.LayoutPrefetchRegistry.addPosition. A negative layoutPosition is meaningless (RecyclerView positions are 0-based and a negative index would index the wrong item or underflow), so addPosition rejects it. This is called from LayoutManager.onInitialPrefetch or nested RecyclerView prefetch; reaching it usually means a custom LayoutManager computed a position with arithmetic that underflowed (e.g. firstVisibleItem - 1 when firstVisibleItem is 0).

Source

Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/GapWorker.java:116

                    // momentum based prefetch, only if we trust current child/adapter state
                    if (!view.hasPendingAdapterUpdates()) {
                        layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
                                view.mState, this);
                    }
                }

                if (mCount > layout.mPrefetchMaxCountObserved) {
                    layout.mPrefetchMaxCountObserved = mCount;
                    layout.mPrefetchMaxObservedInInitialPrefetch = nested;
                    view.mRecycler.updateViewCacheSize();
                }
            }
        }

        @Override
        public void addPosition(int layoutPosition, int pixelDistance) {
            if (layoutPosition < 0) {
                throw new IllegalArgumentException("Layout positions must be non-negative");
            }

            if (pixelDistance < 0) {
                throw new IllegalArgumentException("Pixel distance must be non-negative");
            }

            // allocate or expand array as needed, doubling when needed
            final int storagePosition = mCount * 2;
            if (mPrefetchArray == null) {
                mPrefetchArray = new int[4];
                Arrays.fill(mPrefetchArray, -1);
            } else if (storagePosition >= mPrefetchArray.length) {
                final int[] oldArray = mPrefetchArray;
                mPrefetchArray = new int[storagePosition * 2];
                System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
            }

            // add position

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. In custom prefetch code, clamp positions to >= 0 before calling addPosition.
  2. Skip the addPosition call entirely when getChildAdapterPosition returns RecyclerView.NO_POSITION.
  3. Guard prefetch against empty adapters (getItemCount() == 0).

Example fix

// before
int pos = getChildAdapterPosition(child) - 1;
prefetchRegistry.addPosition(pos, distance); // pos may be -1

// after
int pos = getChildAdapterPosition(child) - 1;
if (pos >= 0 && pos < getAdapter().getItemCount()) {
    prefetchRegistry.addPosition(pos, distance);
}
Defensive patterns

Strategy: validation

Validate before calling

// Clamp prefetch positions to valid range
void safeAddPosition(LayoutPrefetchRegistry reg, int pos, int dist) {
    if (pos < 0 || pos >= getAdapter().getItemCount()) return;
    reg.addPosition(pos, dist);
}

Prevention

When it happens

Trigger: A custom LayoutManager override of collectAdjacentPrefetchPositions or onInitialPrefetch that subtracts from a position without clamping; nested RecyclerView whose inner adapter has 0 items; prefetch logic that uses getChildAdapterPosition which returned NO_POSITION (-1).

Common situations: Custom carousel or staggered LayoutManager with hand-written prefetch; nested horizontal RecyclerView inside a vertical one where the inner list is empty; off-by-one in prefetch distance math after a data clear.

Related errors


AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14). Data as JSON: /api/errors/c3e49d232606a13f. Report an issue: GitHub.