{"record":{"id":"400cd3e4c391bf1f","repo":"airbnb/epoxy","slug":"you-cannot-notify-item-changes-directly-call","errorCode":null,"errorMessage":"You cannot notify item changes directly. Call `requestModelBuild` instead.","messagePattern":"You cannot notify item changes directly\\. Call `requestModelBuild` instead\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/NotifyBlocker.java","lineNumber":27,"sourceCode":" * <p>\n * This observer throws upon any changes done outside of diffing.\n */\nclass NotifyBlocker extends AdapterDataObserver {\n\n  private boolean changesAllowed;\n\n  void allowChanges() {\n    changesAllowed = true;\n  }\n\n  void blockChanges() {\n    changesAllowed = false;\n  }\n\n  @Override\n  public void onChanged() {\n    if (!changesAllowed) {\n      throw new IllegalStateException(\n          \"You cannot notify item changes directly. Call `requestModelBuild` instead.\");\n    }\n  }\n\n  @Override\n  public void onItemRangeChanged(int positionStart, int itemCount) {\n    onChanged();\n  }\n\n  @Override\n  public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {\n    onChanged();\n  }\n\n  @Override\n  public void onItemRangeInserted(int positionStart, int itemCount) {\n    onChanged();\n  }","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/NotifyBlocker.java#L9-L45","documentation":"Epoxy's controller blocks direct adapter notify calls: NotifyBlocker.onChanged throws IllegalStateException because callers must rebuild models via requestModelBuild instead of notifying RecyclerView changes manually. The blocked methods (onChanged and the range variants) are the ones RecyclerView's Adapter exposes for manual change notifications.","triggerScenarios":"Calling adapter.notifyDataSetChanged() / notifyItemChanged/Inserted/Removed/Moved on the adapter managed by an Epoxy controller (any notify variant while changesAllowed == false, which the controller sets).","commonSituations":"Porting standard RecyclerView Adapter code to Epoxy without removing manual notify calls; helper libraries or animations that call notifyDataSetChanged directly.","solutions":["Delete all adapter.notifyXxx calls and let the controller rebuild models","Call controller.requestModelBuild() after changing your data","Ensure adapter data flows through setModels / buildModels only"],"exampleFix":"// before\nadapter.notifyDataSetChanged();\n// after\ncontroller.requestModelBuild();","handlingStrategy":"fallback","validationCode":"if (adapter instanceof NotifyBlocker) { controller.requestModelBuild(); } else { adapter.notifyDataSetChanged(); }","typeGuard":null,"tryCatchPattern":"try { adapter.notifyDataSetChanged(); } catch (IllegalStateException e) { controller.requestModelBuild(); }","preventionTips":["Remove all manual notifyXxx calls after migrating to Epoxy","Treat the controller as the sole owner of adapter updates","Call requestModelBuild() after data changes","Grep for notifyDataSetChanged during migration"],"tags":["android","epoxy","adapter","notify"],"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"}