DrKLO/Telegram · critical · IndexOutOfBoundsException

Inconsistency detected. Invalid item position {position}(off

Error message

Inconsistency detected. Invalid item position {position}(offset:{offsetPosition}).state:{itemCount}

What it means

Recycler.bindViewToPosition throws IndexOutOfBoundsException ('Inconsistency detected. Invalid item position') when mAdapterHelper.findPositionOffset(position) returns a value outside [0, mAdapter.getItemCount()). findPositionOffset translates a pre-layout position into an adapter position by replaying pending notify operations; an out-of-range result means the requested position does not map to a real adapter index after applying pending removes/inserts. As with error 49, this signals that the adapter's pending update events do not match the actual state of the backing data.

Source

Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java:6064

         * and let the RecyclerView handle caching. This is a helper method for LayoutManager who
         * wants to handle its own recycling logic.
         * <p>
         * Note that, {@link #getViewForPosition(int)} already binds the View to the position so
         * you don't need to call this method unless you want to bind this View to another position.
         *
         * @param view The view to update.
         * @param position The position of the item to bind to this View.
         */
        public void bindViewToPosition(@NonNull View view, int position) {
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder == null) {
                throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
                        + " pass arbitrary views to this method, they should be created by the "
                        + "Adapter" + exceptionLabel());
            }
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                        + "position " + position + "(offset:" + offsetPosition + ")."
                        + "state:" + mState.getItemCount() + exceptionLabel());
            }
            tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }

            rvLayoutParams.mInsetsDirty = true;

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Guarantee notify ranges exactly match the backing-list delta — remove N items with notifyItemRangeRemoved(idx, N) and vice versa for inserts; prefer ListAdapter.submitList with a correct DiffUtil to avoid manual range math.
  2. In a custom LayoutManager, always read the adapter count from state.getItemCount() (the Recycler/State APIs), never cache counts across layout passes.
  3. Ensure all mutations and notify calls happen on the main thread, atomically (mutate then immediately notify).
  4. If corruption already occurred, call notifyDataSetChanged() to reset position bookkeeping before further updates.

Example fix

// before
items.addAll(0, newBatch);
notifyItemRangeInserted(0, newBatch.size() - 1); // off-by-one -> invalid offset
// after
int n = newBatch.size();
items.addAll(0, newBatch);
notifyItemRangeInserted(0, n);
Defensive patterns

Strategy: validation

Validate before calling

int count = adapter.getItemCount();
if (position < 0 || position >= count) {
  throw new IndexOutOfBoundsException("position " + position + " out of [0," + count + ")");
}

Prevention

When it happens

Trigger: A LayoutManager requesting a position that exceeds the current adapter count (e.g. after items were removed but the LM still references stale positions); notifyItemRangeRemoved/Inserted with a count that does not match the real list delta; requesting a position during pre-layout that AdapterHelper cannot resolve; concurrent data mutation invalidating position offsets.

Common situations: Custom LayoutManager holding stale position indices; inconsistent notify ranges; rapid add/remove sequences where notify math drifts; background-thread list mutation racing a layout pass.

Related errors


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