airbnb/epoxy · error · IllegalStateException

A model was selected that is not a valid target:

Error message

A model was selected that is not a valid target: 

What it means

Epoxy throws this IllegalStateException from ItemTouchHelper's onSelectedChanged callback when a ViewHolder begins a drag or swipe but its model is not registered as touchable. EpoxyModelTouchCallback only permits models explicitly allowed by isTouchableModel, so an unregistered model being touched indicates a misconfiguration between the adapter's models and the touch callback.

Solutions

  1. Override isTouchableModel in your EpoxyModelTouchCallback subclass to return true for the model class being selected
  2. Ensure the touch callback is attached to a controller/adapter whose models all pass isTouchableModel
  3. Check that the model class being swiped/dragged matches the class registered in the callback (subclass/generics mismatch)

Example fix

// before
@Override
protected boolean isTouchableModel(EpoxyModel<?> model) {
  return model instanceof SwipeOutModel;
}
// after
@Override
protected boolean isTouchableModel(EpoxyModel<?> model) {
  return model instanceof SwipeOutModel || model instanceof DraggableModel;
}
Defensive patterns

Strategy: validation

Validate before calling

if (callback.isTouchableModel(model)) {
  itemTouchHelper.attachToRecyclerView(recyclerView);
}

Type guard

boolean isTouchable(EpoxyModel<?> m) { return m instanceof SwipeOutModel || m instanceof DraggableModel; }

Try / catch

try { touchHelper.onSwiped(vh, dir); } catch (IllegalStateException e) { Log.w(TAG, "non-touchable model selected", e); }

Prevention

When it happens

Trigger: A swipe/drag starts on a view holder whose model is not in the set of touchable models (e.g. the model list contains a model type not handled by isTouchableModel, or touchable model registration was changed/omitted).

Common situations: Adding new model types to a RecyclerView with swipe-to-dismiss or drag-and-drop without updating the touch callback's allowed models; mixing generic EpoxyModel instances into a list intended only for touchable models.

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


AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13). Data as JSON: /api/errors/242ef9d452b39d2d. Report an issue: GitHub.

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModelTouchCallback.java:122

    }

    //noinspection unchecked
    onSwipeCompleted((T) model, view, position, direction);
  }

  @Override
  public void onSwipeCompleted(T model, View itemView, int position, int direction) {

  }

  @Override
  protected void onSelectedChanged(@Nullable EpoxyViewHolder viewHolder, int actionState) {
    super.onSelectedChanged(viewHolder, actionState);

    if (viewHolder != null) {
      EpoxyModel<?> model = viewHolder.getModel();
      if (!isTouchableModel(model)) {
        throw new IllegalStateException(
            "A model was selected that is not a valid target: " + model.getClass());
      }

      markRecyclerViewHasSelection((RecyclerView) viewHolder.itemView.getParent());

      if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        holderBeingSwiped = viewHolder;
        //noinspection unchecked
        onSwipeStarted((T) model, viewHolder.itemView, viewHolder.getAdapterPosition());
      } else if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
        holderBeingDragged = viewHolder;
        //noinspection unchecked
        onDragStarted((T) model, viewHolder.itemView, viewHolder.getAdapterPosition());
      }
    } else if (holderBeingDragged != null) {
      //noinspection unchecked
      onDragReleased((T) holderBeingDragged.getModel(), holderBeingDragged.itemView);
      holderBeingDragged = null;

View on GitHub (pinned to e45bd3a61f)