{"record":{"id":"fc9f038b7711daac","repo":"DrKLO/Telegram","slug":"must-pass-a-viewholder-when-dragging","errorCode":null,"errorMessage":"Must pass a ViewHolder when dragging","messagePattern":"Must pass a ViewHolder when dragging","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"TMessagesProj/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java","lineNumber":591,"sourceCode":"     * Starts dragging or swiping the given View. Call with null if you want to clear it.\n     *\n     * @param selected    The ViewHolder to drag or swipe. Can be null if you want to cancel the\n     *                    current action, but may not be null if actionState is ACTION_STATE_DRAG.\n     * @param actionState The type of action\n     */\n    @SuppressWarnings(\"WeakerAccess\") /* synthetic access */\n    void select(@Nullable ViewHolder selected, int actionState) {\n        if (selected == mSelected && actionState == mActionState) {\n            return;\n        }\n        mDragScrollStartTimeInMs = Long.MIN_VALUE;\n        final int prevActionState = mActionState;\n        // prevent duplicate animations\n        endRecoverAnimation(selected, true);\n        mActionState = actionState;\n        if (actionState == ACTION_STATE_DRAG) {\n            if (selected == null) {\n                throw new IllegalArgumentException(\"Must pass a ViewHolder when dragging\");\n            }\n\n            // we remove after animation is complete. this means we only elevate the last drag\n            // child but that should perform good enough as it is very hard to start dragging a\n            // new child before the previous one settles.\n            mOverdrawChild = selected.itemView;\n            addChildDrawingOrderCallback();\n        }\n        int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))\n                - 1;\n        boolean preventLayout = false;\n\n        if (mSelected != null) {\n            final ViewHolder prevSelected = mSelected;\n            if (prevSelected.itemView.getParent() != null) {\n                final boolean swipeBack = shouldSwipeBack();\n                final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0\n                        : swipeIfNecessary(prevSelected);","sourceCodeStart":573,"sourceCodeEnd":609,"githubUrl":"https://github.com/DrKLO/Telegram/blob/45ab8f4308496e1f01026a97fcdb0d58a5274474/TMessagesProj/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java#L573-L609","documentation":"ItemTouchHelper.select() is called to start/cancel a drag or swipe action. When actionState is ACTION_STATE_DRAG, a non-null ViewHolder is mandatory because dragging requires a concrete itemView to elevate and track (it sets mOverdrawChild = selected.itemView). Passing null while in drag state is an unrecoverable contract violation — there is no view to move.","triggerScenarios":"Calling itemTouchHelper.select(null, ItemTouchHelper.ACTION_STATE_DRAG); directly, or a custom ItemTouchHelper.Callback/extension that invokes startDrag with a null target. Also reachable if a subclass overrides the drag-start path and forwards a null ViewHolder into select() with ACTION_STATE_DRAG.","commonSituations":"Programmatic drag kickoff where the ViewHolder lookup returned null (adapter race, wrong position passed to findViewHolderForAdapterPosition). Custom reorder UI that starts a drag from a handle whose click handler could not resolve the active ViewHolder. Migrating swipe logic and reusing the select() entry point for drag without supplying a target.","solutions":["Resolve the ViewHolder with recyclerView.findViewHolderForAdapterPosition(position) BEFORE calling startDrag/select and bail out if it returns null.","Never call select(..., ACTION_STATE_DRAG) with null; to cancel an in-progress action use ACTION_STATE_IDLE with null instead.","Guard the custom drag entrypoint: if (vh == null) return; before invoking ItemTouchHelper drag."],"exampleFix":"// before\nitemTouchHelper.select(null, ItemTouchHelper.ACTION_STATE_DRAG);\n\n// after\n// to cancel an action, use IDLE, not DRAG\nitemTouchHelper.select(null, ItemTouchHelper.ACTION_STATE_IDLE);\n// or, to start a real drag, resolve the holder first\nViewHolder vh = recyclerView.findViewHolderForAdapterPosition(pos);\nif (vh != null) itemTouchHelper.startDrag(vh);","handlingStrategy":"validation","validationCode":"ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(position);\nif (vh == null) {\n    // resolve later or abort; never start a drag without a holder\n    return;\n}\nitemTouchHelper.startDrag(vh);","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Treat a null ViewHolder from findViewHolderForAdapterPosition as a normal outcome (item recycled) and abort gracefully.","To cancel an action use ACTION_STATE_IDLE, never ACTION_STATE_DRAG with null.","Keep drag-triggering logic inside the RecyclerView's click/touch handlers so the holder is always resolvable."],"tags":["recyclerview","itemtouchhelper","drag-and-drop","validation"],"backgroundTag":null,"analyzedSha":"45ab8f4308496e1f01026a97fcdb0d58a5274474","analyzedAt":"2026-08-14T05:19:30.815Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}