{"record":{"id":"ef99e4c86886c342","repo":"DrKLO/Telegram","slug":"inconsistency-detected-invalid-item-position-pos","errorCode":null,"errorMessage":"Inconsistency detected. Invalid item position {position}(offset:{offsetPosition}).state:{itemCount}","messagePattern":"Inconsistency detected\\. Invalid item position (.+?)\\(offset:(.+?)\\)\\.state:(.+?)","errorType":"exception","errorClass":"IndexOutOfBoundsException","httpStatus":null,"severity":"critical","filePath":"TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java","lineNumber":6064,"sourceCode":"         * and let the RecyclerView handle caching. This is a helper method for LayoutManager who\n         * wants to handle its own recycling logic.\n         * <p>\n         * Note that, {@link #getViewForPosition(int)} already binds the View to the position so\n         * you don't need to call this method unless you want to bind this View to another position.\n         *\n         * @param view The view to update.\n         * @param position The position of the item to bind to this View.\n         */\n        public void bindViewToPosition(@NonNull View view, int position) {\n            ViewHolder holder = getChildViewHolderInt(view);\n            if (holder == null) {\n                throw new IllegalArgumentException(\"The view does not have a ViewHolder. You cannot\"\n                        + \" pass arbitrary views to this method, they should be created by the \"\n                        + \"Adapter\" + exceptionLabel());\n            }\n            final int offsetPosition = mAdapterHelper.findPositionOffset(position);\n            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {\n                throw new IndexOutOfBoundsException(\"Inconsistency detected. Invalid item \"\n                        + \"position \" + position + \"(offset:\" + offsetPosition + \").\"\n                        + \"state:\" + mState.getItemCount() + exceptionLabel());\n            }\n            tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);\n\n            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();\n            final LayoutParams rvLayoutParams;\n            if (lp == null) {\n                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();\n                holder.itemView.setLayoutParams(rvLayoutParams);\n            } else if (!checkLayoutParams(lp)) {\n                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);\n                holder.itemView.setLayoutParams(rvLayoutParams);\n            } else {\n                rvLayoutParams = (LayoutParams) lp;\n            }\n\n            rvLayoutParams.mInsetsDirty = true;","sourceCodeStart":6046,"sourceCodeEnd":6082,"githubUrl":"https://github.com/DrKLO/Telegram/blob/45ab8f4308496e1f01026a97fcdb0d58a5274474/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java#L6046-L6082","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","In a custom LayoutManager, always read the adapter count from state.getItemCount() (the Recycler/State APIs), never cache counts across layout passes.","Ensure all mutations and notify calls happen on the main thread, atomically (mutate then immediately notify).","If corruption already occurred, call notifyDataSetChanged() to reset position bookkeeping before further updates."],"exampleFix":"// before\nitems.addAll(0, newBatch);\nnotifyItemRangeInserted(0, newBatch.size() - 1); // off-by-one -> invalid offset\n// after\nint n = newBatch.size();\nitems.addAll(0, newBatch);\nnotifyItemRangeInserted(0, n);","handlingStrategy":"validation","validationCode":"int count = adapter.getItemCount();\nif (position < 0 || position >= count) {\n  throw new IndexOutOfBoundsException(\"position \" + position + \" out of [0,\" + count + \")\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure every notify range exactly matches the backing-list size delta.","Use ListAdapter.submitList to avoid manual range math.","In custom LayoutManagers, read counts from State each layout, never cache."],"tags":["recyclerview","adapter","notify","position-tracking","data-integrity"],"backgroundTag":null,"analyzedSha":"45ab8f4308496e1f01026a97fcdb0d58a5274474","analyzedAt":"2026-08-14T05:19:30.815Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}