airbnb/epoxy · error · IllegalStateException
A controller must be provided in the constructor if…
Error message
A controller must be provided in the constructor if dragging is enabled
What it means
EpoxyModelTouchCallback moves models by delegating to controller.moveModel(from, to). If dragging is enabled but no controller was supplied to the callback's constructor, a drag's onMove cannot be routed, so Epoxy throws IllegalStateException before performing the move.
Solutions
- Pass the controller into the EpoxyModelTouchCallback constructor
- Ensure the controller is initialized before attaching the ItemTouchHelper
- Only enable dragging (isDragEnabled) when a controller is available
Example fix
// before new MyTouchCallback(null); // drag throws in onMove // after new MyTouchCallback(myEpoxyController);
Defensive patterns
Strategy: type-guard
Validate before calling
if (touchCallback.getController() == null) { throw new IllegalStateException("Attach touch helper only after controller is set"); } Type guard
if (callback == null || callback.getController() == null) return; // skip attaching ItemTouchHelper
Try / catch
try { itemTouchHelper.attachToRecyclerView(rv); } catch (IllegalStateException e) { Log.e(TAG, "Drag without controller", e); } Prevention
- Pass the controller into the touch callback constructor
- Attach ItemTouchHelper only after controller initialization
- Enable dragging only when a controller exists
When it happens
Trigger: Attaching an ItemTouchHelper with an EpoxyModelTouchCallback subclass whose constructor was given a null controller (or whose isDragEnabled returns true) and then dragging an item, triggering onMove.
Common situations: Instantiating the touch callback without passing the EpoxyController; the controller not yet created when the callback is constructed; copy-pasting touch callback setup that omits the controller argument.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- A model was dragged 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…
- numItemsToPrefetch must be greater than 0
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/a856d26bddb68830.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModelTouchCallback.java:71
}
@Override
protected boolean canDropOver(RecyclerView recyclerView, EpoxyViewHolder current,
EpoxyViewHolder target) {
// By default we don't allow dropping on a model that isn't a drag target
return isTouchableModel(target.getModel());
}
protected boolean isTouchableModel(EpoxyModel<?> model) {
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;
}
View on GitHub (pinned to e45bd3a61f)