{"record":{"id":"f3f25517827d3214","repo":"airbnb/epoxy","slug":"you-cannot-call-requestmodelbuild-directly-call-setdata","errorCode":null,"errorMessage":"You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a model refresh with new data.","messagePattern":"You cannot call `requestModelBuild` directly\\. Call `setData` instead to trigger a model refresh with new data\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed2EpoxyController.java","lineNumber":47,"sourceCode":"    super(modelBuildingHandler, diffingHandler);\n  }\n\n  /**\n   * Call this with the latest data when you want models to be rebuilt. The data will be passed on\n   * to {@link #buildModels(Object, Object)}\n   */\n  public void setData(T data1, U data2) {\n    this.data1 = data1;\n    this.data2 = data2;\n    allowModelBuildRequests = true;\n    requestModelBuild();\n    allowModelBuildRequests = false;\n  }\n\n  @Override\n  public final void requestModelBuild() {\n    if (!allowModelBuildRequests) {\n      throw new IllegalStateException(\n          \"You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a \"\n              + \"model refresh with new data.\");\n    }\n    super.requestModelBuild();\n  }\n\n  @Override\n  public void moveModel(int fromPosition, int toPosition) {\n    allowModelBuildRequests = true;\n    super.moveModel(fromPosition, toPosition);\n    allowModelBuildRequests = false;\n  }\n\n  @Override\n  public void requestDelayedModelBuild(int delayMs) {\n    if (!allowModelBuildRequests) {\n      throw new IllegalStateException(\n          \"You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a \"","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed2EpoxyController.java#L29-L65","documentation":"Typed2EpoxyController is a data-driven controller: you feed it data via `setData(data1, data2)`, and it manages when model builds run. `requestModelBuild()` is final and throws `IllegalStateException` unless `allowModelBuildRequests` is true (which is only true briefly inside `setData`). This prevents callers from triggering a rebuild with stale internal data instead of providing fresh data.","triggerScenarios":"Calling `controller.requestModelBuild()` from application code at any point other than indirectly through `setData`. For example, calling it after mutating an object that was previously passed to `setData`, hoping the controller will pick up the change.","commonSituations":"Porting code from base `EpoxyController` (where `requestModelBuild()` is the normal API) to a Typed controller without switching to `setData`; calling `requestModelBuild()` in a callback/listener after data changed outside the controller; copy-pasting refresh logic between typed and untyped controllers.","solutions":["Call `controller.setData(newData1, newData2)` with the updated data instead of `requestModelBuild()`.","Keep a copy of the current data in your screen/fragment, mutate it, and pass the full new pair back through `setData`.","If you need a delayed rebuild, pass new data via `setData`; the controller schedules the build itself.","Refactor the subclass's `buildModels(data1, data2)` to derive everything from the data parameters rather than mutable external state."],"exampleFix":"// before\nitems.add(newItem);\ncontroller.requestModelBuild(); // IllegalStateException\n\n// after\nitems.add(newItem);\ncontroller.setData(items, otherData);","handlingStrategy":"validation","validationCode":"// Typed2EpoxyController: refresh only through setData\n// if (controller is Typed2EpoxyController<*, *>) controller.setData(d1, d2) else controller.requestModelBuild()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember Typed* controllers are data-driven: any change goes through setData.","Grep your codebase for `requestModelBuild()` and remove calls on Typed controllers.","Avoid mutating objects already passed to setData; create and pass new data.","When migrating controller types, update all refresh call sites."],"tags":["android","epoxy","lifecycle","api-misuse"],"backgroundTag":"unsupported-operation","analyzedSha":"e45bd3a61fe3a1f130e184f5b8dcf172ab99025a","analyzedAt":"2026-09-13T03:24:16.052Z","contentChangedAt":"2026-09-13T03:24:16.052Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}