{"record":{"id":"e000419be2c1e4d1","repo":"airbnb/epoxy","slug":"must-have-stable-ids-when-saving-view-holder-state","errorCode":null,"errorMessage":"Must have stable ids when saving view holder state","messagePattern":"Must have stable ids when saving view holder state","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/BaseEpoxyAdapter.java","lineNumber":232,"sourceCode":"  }\n\n  @CallSuper\n  @Override\n  public void onViewDetachedFromWindow(EpoxyViewHolder holder) {\n    //noinspection unchecked,rawtypes\n    ((EpoxyModel) holder.getModel()).onViewDetachedFromWindow(holder.objectToBind());\n  }\n\n  public void onSaveInstanceState(Bundle outState) {\n    // Save the state of currently bound views first so they are included. Views that were\n    // scrolled off and unbound will already have had\n    // their state saved.\n    for (EpoxyViewHolder holder : boundViewHolders) {\n      viewHolderState.save(holder);\n    }\n\n    if (viewHolderState.size() > 0 && !hasStableIds()) {\n      throw new IllegalStateException(\"Must have stable ids when saving view holder state\");\n    }\n\n    outState.putParcelable(SAVED_STATE_ARG_VIEW_HOLDERS, viewHolderState);\n  }\n\n  public void onRestoreInstanceState(@Nullable Bundle inState) {\n    // To simplify things we enforce that state is restored before views are bound, otherwise it\n    // is more difficult to update view state once they are bound\n    if (boundViewHolders.size() > 0) {\n      throw new IllegalStateException(\n          \"State cannot be restored once views have been bound. It should be done before adding \"\n              + \"the adapter to the recycler view.\");\n    }\n\n    if (inState != null) {\n      viewHolderState = inState.getParcelable(SAVED_STATE_ARG_VIEW_HOLDERS);\n      if (viewHolderState == null) {\n        throw new IllegalStateException(","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/BaseEpoxyAdapter.java#L214-L250","documentation":"BaseEpoxyAdapter saves view-holder state (scroll positions, subview state) in onSaveInstanceState, but restoring that state later requires matching each saved item by adapter item id. If the adapter does not have stable ids enabled, saved state cannot be mapped back to models, so the library throws to prevent silently losing state.","triggerScenarios":"Calling epoxyRecyclerView.saveState() / adapter.onSaveInstanceState(bundle) when hasStableIds() is false and at least one view holder is bound with saved state (viewHolderState.size() > 0).","commonSituations":"Enabling state saving (e.g. withDisabledParenStateSaving off, carousel state saving) on an EpoxyRecyclerView whose models lack unique ids or whose adapter was built without setting hasStableIds(true). Common with models using auto-generated or duplicate ids.","solutions":["Ensure all models have unique, stable ids (use @AutoModel, id(...), or distinct hash-based ids).","Enable stable ids on the adapter: adapter.setHasStableIds(true) or build models with stable ids so Epoxy sets it.","If stable ids are not possible, do not enable view-holder state saving for that adapter.","Call onSaveInstanceState only when you know stable ids are enabled."],"exampleFix":"// before\nEpoxyRecyclerView recyclerView = ...;\n// models without explicit ids, state saving enabled\nBundle state = recyclerView.saveState();\n\n// after\nadapter.setHasStableIds(true); // or use @AutoModel / model.id(stableId)\nBundle state = recyclerView.saveState();","handlingStrategy":"validation","validationCode":"if (adapter.hasStableIds()) { Bundle out = new Bundle(); adapter.onSaveInstanceState(out); } else { /* skip state saving or assign stable ids first */ }","typeGuard":null,"tryCatchPattern":"try { adapter.onSaveInstanceState(bundle); } catch (IllegalStateException e) { Log.w(\"Epoxy\", \"state save skipped: \" + e.getMessage()); }","preventionTips":["Always assign explicit stable ids to models (model.id(...), @AutoModel).","Enable setHasStableIds(true) whenever you plan to save view state.","Test state save/restore across configuration changes early."],"tags":["android","epoxy","state-saving","stable-ids"],"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"}