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

  1. Pass the controller into the EpoxyModelTouchCallback constructor
  2. Ensure the controller is initialized before attaching the ItemTouchHelper
  3. 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

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


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)