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

  1. Clamp/guard the input position: ensure 0 <= position < state.getItemCount() before calling; re-read count from the current State each time.
  2. Do not cache positions across layout passes in a custom LayoutManager — derive them fresh from State each layout.
  3. 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

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


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