DrKLO/Telegram · error · IllegalArgumentException
Moving more than 1 item is not supported yet
Error message
Moving more than 1 item is not supported yet
What it means
AdapterHelper.onItemRangeMoved is the entry point for adapter.notifyItemMoved(from, to). RecyclerView's move model is strictly single-item: a move always relocates exactly one element. There is no notifyItemRangeMoved API that moves a contiguous block, so itemCount must be 1. Passing itemCount != 1 is invalid and unsupported, so the helper refuses it rather than silently producing a wrong layout. Most commonly this is hit by custom adapter code that calls the internal preUpdate or onItemRangeMoved directly with a range.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/AdapterHelper.java:587
return false;
}
if (BuildVars.DEBUG_VERSION) {
logNotify("onItemRangeRemoved(" + positionStart + ", " + itemCount + ")");
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.REMOVE;
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeMoved(int from, int to, int itemCount) {
if (from == to) {
return false; // no-op
}
if (itemCount != 1) {
throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
}
if (BuildVars.DEBUG_VERSION) {
logNotify("onItemRangeMoved(" + from + ", " + to + ", " + itemCount + ")");
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null));
mExistingUpdateTypes |= UpdateOp.MOVE;
return mPendingUpdates.size() == 1;
}
/**
* Skips pre-processing and applies all updates in one pass.
*/
void consumeUpdatesInOnePass() {
// we still consume postponed updates (if there is) in case there was a pre-process call
// w/o a matching consumePostponedUpdates.
consumePostponedUpdates();
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {View on GitHub (pinned to 45ab8f4308)
Solutions
- Replace a multi-item move with one notifyItemMoved per item, or better, express it as remove + insert and let DiffUtil batch it.
- If implementing drag-to-reorder, call notifyItemMoved once per swapped adjacent pair.
- Never call onItemRangeMoved directly from app code; use the public RecyclerView.Adapter.notifyItemMoved(int,int).
Example fix
// before
// attempting to move 3 items at once
adapterHelper.onItemRangeMoved(from, to, 3); // throws
// after
for (int i = 0; i < 3; i++) {
adapter.notifyItemMoved(from + i, to + i);
} Defensive patterns
Strategy: validation
Validate before calling
// Enforce single-item move contract before notifying
void safeMove(RecyclerView.Adapter<?> a, int from, int to, int count) {
if (count != 1) throw new IllegalArgumentException("move count must be 1, was " + count);
a.notifyItemMoved(from, to);
} Prevention
- Only call the public notifyItemMoved(int,int) — never onItemRangeMoved directly.
- Express multi-item reorders as remove+insert or let DiffUtil batch them.
- In drag-and-drop, emit one notify per adjacent swap.
When it happens
Trigger: A custom adapter that calls adapterHelper.onItemRangeMoved(from, to, n) with n>1; wrapping notifyItemMoved inside a loop without DiffUtil; a data model where a 'move' is actually a bulk reordering incorrectly mapped onto a single move op.
Common situations: Bulk drag-and-drop reorder where the developer assumes a multi-item move primitive exists; porting code from a list library that supports range moves; a DiffUtil-like custom diff that emits multi-item moves instead of remove+insert pairs.
Related errors
- should not dispatch add or move for pre layout
- Must pass a ViewHolder when dragging
- Two different ViewHolders have the same stable ID. Stable ID
- Two different ViewHolders have the same change ID. This migh
- Inconsistency detected. Invalid view holder adapter position
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/b0d99955efc59db0.
Report an issue: GitHub.