airbnb/epoxy · error · IllegalStateException
You cannot call `buildModels` directly. Call `setData`…
Error message
You cannot call `buildModels` directly. Call `setData` instead to trigger a model refresh with new data.
What it means
Typed4EpoxyController implements buildModels() as final and only allows it to run while an internal model build (started by setData) is in progress. Calling it yourself from outside that flow throws this IllegalStateException. It exists to force users to supply data via setData instead of building models manually.
Solutions
- Replace the direct buildModels() call with controller.setData(d1, d2, d3, d4)
- Move your data update so the controller's setData overload is the single entry point that triggers the rebuild
- If a delayed build is needed, use setData with the typed data rather than requestDelayedModelBuild
Example fix
// before controller.buildModels(); // after controller.setData(data1, data2, data3, data4);
Defensive patterns
Strategy: validation
Validate before calling
if (controller is Typed4EpoxyController) { controller.setData(d1, d2, d3, d4); } else { controller.buildModels(); } Type guard
fun isTyped4Controller(c: EpoxyController) = c is Typed4EpoxyController<*,*,*,*>
Prevention
- Never call buildModels on typed controllers; expose only setData in your wrappers
- Centralize model rebuild triggers in one helper
- Review usages of buildModels when migrating from base EpoxyController
When it happens
Trigger: Calling controller.buildModels() directly on a Typed4EpoxyController subclass from application code, so isBuildingModels() is false at that moment.
Common situations: Migrating code from a plain EpoxyController (where buildModels is the override point) to a TypedEpoxyController and keeping an explicit buildModels() call; calling buildModels after updating data manually instead of using setData.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- You cannot call `requestModelBuild` directly. Call…
- You cannot call `buildModels` directly. Call `setData`…
- A controller must be set before requesting a model build.
- Models were set with #setModels, they can not be rebuilt.
- You must enable diffing before notifying models changed
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/00edbe7cce1af5aa.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed4EpoxyController.java:78
allowModelBuildRequests = true;
super.moveModel(fromPosition, toPosition);
allowModelBuildRequests = false;
}
@Override
public void requestDelayedModelBuild(int delayMs) {
if (!allowModelBuildRequests) {
throw new IllegalStateException(
"You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a "
+ "model refresh with new data.");
}
super.requestDelayedModelBuild(delayMs);
}
@Override
protected final void buildModels() {
if (!isBuildingModels()) {
throw new IllegalStateException(
"You cannot call `buildModels` directly. Call `setData` instead to trigger a model "
+ "refresh with new data.");
}
buildModels(data1, data2, data3, data4);
}
protected abstract void buildModels(T data1, U data2, V data3, W data4);
}
View on GitHub (pinned to e45bd3a61f)