{"record":{"id":"45ff2436fd4ca56e","repo":"airbnb/epoxy","slug":"notifications-already-resumed","errorCode":null,"errorMessage":"Notifications already resumed","messagePattern":"Notifications already resumed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelList.java","lineNumber":46,"sourceCode":"\n  interface ModelListObserver {\n    void onItemRangeInserted(int positionStart, int itemCount);\n    void onItemRangeRemoved(int positionStart, int itemCount);\n  }\n\n  private boolean notificationsPaused;\n  private ModelListObserver observer;\n\n  void pauseNotifications() {\n    if (notificationsPaused) {\n      throw new IllegalStateException(\"Notifications already paused\");\n    }\n    notificationsPaused = true;\n  }\n\n  void resumeNotifications() {\n    if (!notificationsPaused) {\n      throw new IllegalStateException(\"Notifications already resumed\");\n    }\n    notificationsPaused = false;\n  }\n\n  void setObserver(ModelListObserver observer) {\n    this.observer = observer;\n  }\n\n  private void notifyInsertion(int positionStart, int itemCount) {\n    if (!notificationsPaused && observer != null) {\n      observer.onItemRangeInserted(positionStart, itemCount);\n    }\n  }\n\n  private void notifyRemoval(int positionStart, int itemCount) {\n    if (!notificationsPaused && observer != null) {\n      observer.onItemRangeRemoved(positionStart, itemCount);\n    }","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelList.java#L28-L64","documentation":"This IllegalStateException is a symmetric state guard in ModelList.resumeNotifications. The class tracks pause/resume of change notifications with a boolean flag; resumeNotifications is only valid when notifications are currently paused. It throws when called while notificationsPaused is false — i.e. resumeNotifications() was called without a prior, still-active pauseNotifications() call (never paused, or already resumed). The source block shows a generic boolean state guard, so the faulting input is the invalid call sequence: a second resumeNotifications() after notifications were already resumed, or a resume with no matching pause. Callers should pair every pauseNotifications()/resumeNotifications() in balanced, non-reentrant blocks (e.g. begin/end or try/finally around batch mutations).","triggerScenarios":"Calling resumeNotifications() when notificationsPaused is false (never paused, or already resumed).","commonSituations":"Unbalanced try/finally around list mutations; resuming after an exception already resumed the list; calling internal API from app code.","solutions":["Wrap mutations so resume is only called once per successful pause","Track pause state in app code or use a boolean guard before resuming","Avoid calling internal ModelList.resumeNotifications directly","Fix exceptions thrown between pause and resume so the pair stays balanced"],"exampleFix":"// before\nlist.resumeNotifications(); // may throw if not paused\n// after\nif (paused) {\n  list.resumeNotifications();\n  paused = false;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { list.resumeNotifications(); } catch (IllegalStateException e) { Log.w(TAG, \"not paused\", e); }","preventionTips":["Only resume after a successful pause","Use try/finally so exceptions don't leave the list in a paused state","Track pause state locally before calling resume","Don't call internal APIs from application code"],"tags":["android","epoxy","model-list","reentrancy"],"backgroundTag":"invalid-state-transition","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"}