DrKLO/Telegram · error · IllegalArgumentException

Must pass a ViewHolder when dragging

Error message

Must pass a ViewHolder when dragging

What it means

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.

Source

Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java:591

     * Starts dragging or swiping the given View. Call with null if you want to clear it.
     *
     * @param selected    The ViewHolder to drag or swipe. Can be null if you want to cancel the
     *                    current action, but may not be null if actionState is ACTION_STATE_DRAG.
     * @param actionState The type of action
     */
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void select(@Nullable ViewHolder selected, int actionState) {
        if (selected == mSelected && actionState == mActionState) {
            return;
        }
        mDragScrollStartTimeInMs = Long.MIN_VALUE;
        final int prevActionState = mActionState;
        // prevent duplicate animations
        endRecoverAnimation(selected, true);
        mActionState = actionState;
        if (actionState == ACTION_STATE_DRAG) {
            if (selected == null) {
                throw new IllegalArgumentException("Must pass a ViewHolder when dragging");
            }

            // we remove after animation is complete. this means we only elevate the last drag
            // child but that should perform good enough as it is very hard to start dragging a
            // new child before the previous one settles.
            mOverdrawChild = selected.itemView;
            addChildDrawingOrderCallback();
        }
        int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
                - 1;
        boolean preventLayout = false;

        if (mSelected != null) {
            final ViewHolder prevSelected = mSelected;
            if (prevSelected.itemView.getParent() != null) {
                final boolean swipeBack = shouldSwipeBack();
                final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
                        : swipeIfNecessary(prevSelected);

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Resolve the ViewHolder with recyclerView.findViewHolderForAdapterPosition(position) BEFORE calling startDrag/select and bail out if it returns null.
  2. Never call select(..., ACTION_STATE_DRAG) with null; to cancel an in-progress action use ACTION_STATE_IDLE with null instead.
  3. Guard the custom drag entrypoint: if (vh == null) return; before invoking ItemTouchHelper drag.

Example fix

// before
itemTouchHelper.select(null, ItemTouchHelper.ACTION_STATE_DRAG);

// after
// to cancel an action, use IDLE, not DRAG
itemTouchHelper.select(null, ItemTouchHelper.ACTION_STATE_IDLE);
// or, to start a real drag, resolve the holder first
ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(pos);
if (vh != null) itemTouchHelper.startDrag(vh);
Defensive patterns

Strategy: validation

Validate before calling

ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(position);
if (vh == null) {
    // resolve later or abort; never start a drag without a holder
    return;
}
itemTouchHelper.startDrag(vh);

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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