airbnb/epoxy · error · IllegalArgumentException

Checked change listener cannot be null

Error message

Checked change listener cannot be null

What it means

WrappedEpoxyModelCheckedChangeListener wraps a delegate OnModelCheckedChangeListener and refuses null in its constructor, throwing this IllegalArgumentException. A null listener would only cause an NPE later, so it fails fast.

Solutions

  1. Ensure the listener is created before wiring it to the model
  2. Guard against null at the call site or provide a no-op listener instead of null
  3. Make the listener non-null by construction (object expression or lambda)

Example fix

// before
model.onCheckedChangeListener(nullableListener); // wraps to IllegalArgument
// after
if (listener == null) listener = (model, view, isChecked) -> {};
model.onCheckedChangeListener(listener);
Defensive patterns

Strategy: validation

Validate before calling

requireNotNull(listener) { "Checked change listener cannot be null" }

Type guard

fun <T,V> validListener(l: OnModelCheckedChangeListener<T,V>?) = l ?: object : OnModelCheckedChangeListener<T,V> { override fun onCheckedChanged(m: T, v: V, isChecked: Boolean) {} }

Try / catch

try { model.onCheckedChangeListener(listener) } catch (e: IllegalArgumentException) { Log.w(TAG, "null listener skipped") }

Prevention

When it happens

Trigger: new WrappedEpoxyModelCheckedChangeListener<>(null), typically when a listener variable is null at wiring time.

Common situations: Passing a listener field not yet initialized; conditionally created listeners that end up null; Kotlin/Java interop passing a null from an optional callback.

Related errors


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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/WrappedEpoxyModelCheckedChangeListener.java:21

import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

import androidx.recyclerview.widget.RecyclerView;

/**
 * Used in the generated models to transform normal checked change listener to model
 * checked change.
 */
public class WrappedEpoxyModelCheckedChangeListener<T extends EpoxyModel<?>, V>
    implements OnCheckedChangeListener {

  private final OnModelCheckedChangeListener<T, V> originalCheckedChangeListener;

  public WrappedEpoxyModelCheckedChangeListener(
      OnModelCheckedChangeListener<T, V> checkedListener
  ) {
    if (checkedListener == null) {
      throw new IllegalArgumentException("Checked change listener cannot be null");
    }

    this.originalCheckedChangeListener = checkedListener;
  }

  @Override
  public void onCheckedChanged(CompoundButton button, boolean isChecked) {
    EpoxyViewHolder epoxyHolder = ListenersUtils.getEpoxyHolderForChildView(button);
    if (epoxyHolder == null) {
      // Initial binding can trigger the checked changed listener when the checked value is set.
      // The view is not attached at this point so the holder can't be looked up, and in any case
      // it is generally better to not trigger a callback for the binding anyway, since it isn't
      // a user action.
      //
      // https://github.com/airbnb/epoxy/issues/797
      return;
    }

View on GitHub (pinned to e45bd3a61f)