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 positionView on GitHub (pinned to 45ab8f4308)
Solutions
- In custom prefetch code, clamp positions to >= 0 before calling addPosition.
- Skip the addPosition call entirely when getChildAdapterPosition returns RecyclerView.NO_POSITION.
- 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
- Treat RecyclerView.NO_POSITION (-1) from getChildAdapterPosition as 'skip prefetch'.
- Clamp every computed prefetch position to >= 0 and < getItemCount.
- Guard against empty adapters before prefetching.
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
- Pixel distance must be non-negative
- view is not a child, cannot hide {}
- invalid orientation:{}
- invalid orientation
- {positionDescription}: Error creating LayoutManager {classNa
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/c3e49d232606a13f.
Report an issue: GitHub.