DrKLO/Telegram · error · IndexOutOfBoundsException
invalid position {position}. State item count is {itemCount}
Error message
invalid position {position}. State item count is {itemCount} What it means
Recycler.convertPreLayoutPositionToPostLayout throws IndexOutOfBoundsException ('invalid position') when the supplied position is outside [0, mState.getItemCount()). This method maps a pre-layout position to its post-layout adapter position; it requires the input to be a valid position in the current State's item count (which during pre-layout includes items pending removal). Passing a negative or >= itemCount position violates the precondition before the offset translation is even attempted.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java:6107
* automatically maps these positions to {@link Adapter} positions when
* {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called.
* <p>
* Usually, LayoutManager does not need to worry about this. However, in some cases, your
* LayoutManager may need to call some custom component with item positions in which
* case you need the actual adapter position instead of the pre layout position. You
* can use this method to convert a pre-layout position to adapter (post layout) position.
* <p>
* Note that if the provided position belongs to a deleted ViewHolder, this method will
* return -1.
* <p>
* Calling this method in post-layout state returns the same value back.
*
* @param position The pre-layout position to convert. Must be greater or equal to 0 and
* less than {@link State#getItemCount()}.
*/
public int convertPreLayoutPositionToPostLayout(int position) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("invalid position " + position + ". State "
+ "item count is " + mState.getItemCount() + exceptionLabel());
}
if (!mState.isPreLayout()) {
return position;
}
return mAdapterHelper.findPositionOffset(position);
}
/**
* Obtain a view initialized for the given position.
*
* This method should be used by {@link LayoutManager} implementations to obtain
* views to represent data from an {@link Adapter}.
* <p>
* The Recycler may reuse a scrap or detached view from a shared pool if one is
* available for the correct view type. If the adapter has not indicated that the
* data at the given position has changed, the Recycler will attempt to hand back
* a scrap view that was previously initialized for that data without rebinding.View on GitHub (pinned to 45ab8f4308)
Solutions
- Clamp/guard the input position: ensure 0 <= position < state.getItemCount() before calling; re-read count from the current State each time.
- Do not cache positions across layout passes in a custom LayoutManager — derive them fresh from State each layout.
- Ensure the call is only made during pre-layout (State.isPreLayout()); in post-layout the conversion is the identity and the bounds check still applies.
Example fix
// before (custom LayoutManager)
int postPos = recycler.convertPreLayoutPositionToPostLayout(cachedPos); // cachedPos stale
// after
int count = state.getItemCount();
if (cachedPos >= 0 && cachedPos < count) {
int postPos = recycler.convertPreLayoutPositionToPostLayout(cachedPos);
} else {
// position no longer valid; skip or recompute
} Defensive patterns
Strategy: validation
Validate before calling
int count = state.getItemCount();
if (position >= 0 && position < count) {
int post = recycler.convertPreLayoutPositionToPostLayout(position);
} else {
// invalid; skip or recompute
} Prevention
- In custom LayoutManagers, derive positions fresh from State each pass.
- Only call convertPreLayoutPositionToPostLayout during pre-layout (State.isPreLayout()).
- Never cache positions across state resets.
When it happens
Trigger: A LayoutManager computing pre-layout positions passing a stale or out-of-range index; calling this outside a layout pass with a position derived from a previous, now-invalid state; off-by-one where a position equal to getItemCount() is passed (must be < count).
Common situations: Custom LayoutManager caching positions across state resets; calling convertPreLayoutPositionToPostLayout during post-layout (where it returns the position unchanged but still validates bounds); adapter count shrunk between position capture and conversion call.
Related errors
- should not dispatch add or move for pre layout
- view is not a child, cannot hide {}
- Layout positions must be non-negative
- Pixel distance must be non-negative
- invalid orientation:{}
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/4e022f2ee23a672c.
Report an issue: GitHub.