{"record":{"id":"4ad83c2ca982164b","repo":"DrKLO/Telegram","slug":"two-different-viewholders-have-the-same-change-id","errorCode":null,"errorMessage":"Two different ViewHolders have the same change ID. This might happen due to inconsistent Adapter update events or if the LayoutManager lays out the same View multiple times.\n ViewHolder 1:{other} \n View Holder 2:{holder}","messagePattern":"Two different ViewHolders have the same change ID\\. This might happen due to inconsistent Adapter update events or if the LayoutManager lays out the same View multiple times\\.\n ViewHolder 1:(.+?) \n View Holder 2:(.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java","lineNumber":4280,"sourceCode":"    private void handleMissingPreInfoForChangeError(long key,\n            ViewHolder holder, ViewHolder oldChangeViewHolder) {\n        // check if two VH have the same key, if so, print that as an error\n        final int childCount = mChildHelper.getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            View view = mChildHelper.getChildAt(i);\n            ViewHolder other = getChildViewHolderInt(view);\n            if (other == holder) {\n                continue;\n            }\n            final long otherKey = getChangedHolderKey(other);\n            if (otherKey == key) {\n                if (mAdapter != null && mAdapter.hasStableIds()) {\n                    throw new IllegalStateException(\"Two different ViewHolders have the same stable\"\n                            + \" ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT\"\n                            + \" change.\\n ViewHolder 1:\" + other + \" \\n View Holder 2:\" + holder\n                            + exceptionLabel());\n                } else {\n                    throw new IllegalStateException(\"Two different ViewHolders have the same change\"\n                            + \" ID. This might happen due to inconsistent Adapter update events or\"\n                            + \" if the LayoutManager lays out the same View multiple times.\"\n                            + \"\\n ViewHolder 1:\" + other + \" \\n View Holder 2:\" + holder\n                            + exceptionLabel());\n                }\n            }\n        }\n        // Very unlikely to happen but if it does, notify the developer.\n        Log.e(TAG, \"Problem while matching changed view holders with the new\"\n                + \"ones. The pre-layout information for the change holder \" + oldChangeViewHolder\n                + \" cannot be found but it is necessary for \" + holder + exceptionLabel());\n    }\n\n    /**\n     * Records the animation information for a view holder that was bounced from hidden list. It\n     * also clears the bounce back flag.\n     */\n    void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder,","sourceCodeStart":4262,"sourceCodeEnd":4298,"githubUrl":"https://github.com/DrKLO/Telegram/blob/45ab8f4308496e1f01026a97fcdb0d58a5274474/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java#L4262-L4298","documentation":"Same code path as error 42 but the else branch: two distinct attached ViewHolders share a change key while hasStableIds() is false. With stable ids off, the change key is position-derived, so a collision means either the LayoutManager laid out the same logical position twice, or the adapter emitted an inconsistent sequence of notify* events (e.g. notifyItemRangeChanged overlapping notifyItemRemoved) that confused AdapterHelper's position bookkeeping. The framework detects the ambiguity during Step 3 change matching and aborts.","triggerScenarios":"A custom LayoutManager that adds the same view/position more than once in a single layout pass; calling notifyItemXxx events whose net effect contradicts each other (e.g. notifyItemInserted then notifyItemRangeRemoved covering overlapping ranges without a matching count change); off-by-one in notify range math so AdapterHelper's predicted positions drift; mutating the backing list without a corresponding notify call.","commonSituations":"Adapter backing list modified (add/remove) but no matching notifyItemXxx issued, then a layout pass runs and the position math collides; a diff algorithm producing conflicting move+change events; RecyclerView used with a data source updated from a non-main thread.","solutions":["Verify every backing-list mutation has a matching, correctly-ranged notifyItemXxx call (or switch to DiffUtil/ListAdapter which computes correct events automatically).","Ensure all adapter updates run on the main thread — background mutations race the layout pass and corrupt AdapterHelper state.","If using a custom LayoutManager, confirm it does not add the same child/position twice in one layout pass; prefer using standard LinearLayoutManager/GridLayoutManager.","Replace manual notify sequences with ListAdapter + DiffUtil to guarantee a consistent, minimal update event stream."],"exampleFix":"// before: mutating list without matching notify, overlapping ranges\nitems.remove(index);\nitems.add(index, newItem);\nnotifyItemChanged(index); // count math wrong if size changed\n// after: use ListAdapter + DiffUtil, or correct paired notifications\nitems.remove(index);\nnotifyItemRemoved(index);\nitems.add(index, newItem);\nnotifyItemInserted(index);","handlingStrategy":"validation","validationCode":"// Before submitting an update, ensure each notify range matches the real delta\nstatic void safeRemove(List<?> list, int idx, int count) {\n  if (idx < 0 || idx + count > list.size()) {\n    throw new IllegalArgumentException(\"notify range out of bounds\");\n  }\n  list.subList(idx, idx + count).clear();\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Replace manual notifyItemXxx sequences with ListAdapter + DiffUtil.","Mutate the backing list and call notify on the same main-thread tick, atomically.","Never mutate the list from a background thread."],"tags":["recyclerview","adapter","notify","data-integrity"],"backgroundTag":null,"analyzedSha":"45ab8f4308496e1f01026a97fcdb0d58a5274474","analyzedAt":"2026-08-14T05:19:30.815Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}