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
- 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.
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
- 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.
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
- Moving more than 1 item is not supported yet
- invalid orientation:{}
- snap preference should be one of the constants defined in Sm
- invalid orientation
- {index} is an invalid index for size {size}
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/fc9f038b7711daac.
Report an issue: GitHub.