airbnb/epoxy · error · IllegalStateException
A model was dragged that is not a valid target:
Error message
A model was dragged that is not a valid target:
What it means
After a drag move completes, EpoxyModelTouchCallback verifies the dragged view holder's model is one of the touchable model types the callback accepts (isTouchableModel). If not, it throws IllegalStateException with the model's class so you know your drag handling touched a model it cannot handle.
Solutions
- Restrict dragging to valid models by overriding isDragEnabled(EpoxyViewHolder, int) to return true only for touchable models
- Update isTouchableModel to cover all model types that can actually be dragged
- Use a narrower ItemTouchHelper setup (e.g. no long-press drag) so only intended models can be dragged
Example fix
// before
@Override public boolean isDragEnabled(EpoxyViewHolder vh, int pos) { return true; } // any model draggable
// after
@Override public boolean isDragEnabled(EpoxyViewHolder vh, int pos) { return isTouchableModel(vh.getModel()); } Defensive patterns
Strategy: type-guard
Validate before calling
if (!touchCallback.isTouchableModel(viewHolder.getModel())) { return false; } // don't start drag Type guard
if (viewHolder.getModel() instanceof MyTouchableModel_) { /* safe to drag */ } Try / catch
try { processMove(from, to); } catch (IllegalStateException e) { Log.e(TAG, "Dragged non-touchable model", e); } Prevention
- Gate isDragEnabled per model type
- Keep isTouchableModel in sync with all draggable models
- Avoid global long-press drag enablement
When it happens
Trigger: A drag is initiated on a model for which isTouchableModel(model) returns false — e.g. dragging a header or any non-T model that ItemTouchHelper still considers movable.
Common situations: isDragEnabled/isMovementWide flags allowing drags on all items while isTouchableModel only accepts a subset; adapters where multiple model types exist but drag setup doesn't filter by type; long-press default drag enabled on every row.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- A controller must be provided in the constructor if…
- A model was swiped that is not a valid target:
- Must have stable ids when saving view holder state
- State cannot be restored once views have been bound. It…
- Tried to restore instance state, but onSaveInstanceState…
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/bd907b5da17cbc59.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModelTouchCallback.java:81
return targetModelClass.isInstance(model);
}
@Override
protected boolean onMove(RecyclerView recyclerView, EpoxyViewHolder viewHolder,
EpoxyViewHolder target) {
if (controller == null) {
throw new IllegalStateException(
"A controller must be provided in the constructor if dragging is enabled");
}
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
controller.moveModel(fromPosition, toPosition);
EpoxyModel<?> model = viewHolder.getModel();
if (!isTouchableModel(model)) {
throw new IllegalStateException(
"A model was dragged that is not a valid target: " + model.getClass());
}
//noinspection unchecked
onModelMoved(fromPosition, toPosition, (T) model, viewHolder.itemView);
return true;
}
@Override
public void onModelMoved(int fromPosition, int toPosition, T modelBeingMoved, View itemView) {
}
@Override
protected void onSwiped(EpoxyViewHolder viewHolder, int direction) {
EpoxyModel<?> model = viewHolder.getModel();
View view = viewHolder.itemView;
int position = viewHolder.getAdapterPosition();View on GitHub (pinned to e45bd3a61f)