{"record":{"id":"0dbc307f65f4345d","repo":"airbnb/epoxy","slug":"state-cannot-be-restored-once-views-have-been-bound-it","errorCode":null,"errorMessage":"State cannot be restored once views have been bound. It should be done before adding the adapter to the recycler view.","messagePattern":"State cannot be restored once views have been bound\\. It should be done before adding the adapter to the recycler view\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/BaseEpoxyAdapter.java","lineNumber":242,"sourceCode":"    // 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(\n            \"Tried to restore instance state, but onSaveInstanceState was never called.\");\n      }\n    }\n  }\n\n  /**\n   * Finds the position of the given model in the list. Doesn't use indexOf to avoid unnecessary\n   * equals() calls since we're looking for the same object instance.\n   *\n   * @return The position of the given model in the current models list, or -1 if the model can't be","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/BaseEpoxyAdapter.java#L224-L260","documentation":"onRestoreInstanceState restores saved view-holder state, but applying it to already-bound views is not supported; Epoxy simplifies state restoration by only allowing it before any views are bound. If boundViewHolders is non-empty it throws instead of producing inconsistent UI state.","triggerScenarios":"Calling adapter.onRestoreInstanceState(bundle) or recyclerView.restoreState(...) after the adapter has been attached to a RecyclerView and at least one view has been bound (boundViewHolders.size() > 0).","commonSituations":"Restoring state in onResume/onPostExecute after the RecyclerView already laid out, or calling restoreState on a second RecyclerView sharing the adapter that already scrolled/bound items.","solutions":["Restore state before adding the adapter to the RecyclerView (e.g. in onCreate before setContentView lays out, or immediately after creating the adapter).","If state must be restored late, create a fresh adapter/RecyclerView, restore state on it before binding, then attach.","Defer adapter attachment: set the adapter only after calling onRestoreInstanceState."],"exampleFix":"// before\nrecyclerView.setController(controller);\ncontroller.requestModelBuild();\nrecyclerView.restoreState(savedState); // throws: views already bound\n\n// after\nadapter.onRestoreInstanceState(savedState);\nrecyclerView.setAdapter(adapter); // restore before attaching/binding","handlingStrategy":"validation","validationCode":"if (adapter.boundViewHolders == null || /* cannot access; instead */ !recyclerView.hasPendingAdapterUpdates() && recyclerView.getChildCount() == 0) { adapter.onRestoreInstanceState(bundle); }","typeGuard":null,"tryCatchPattern":"try { adapter.onRestoreInstanceState(bundle); } catch (IllegalStateException e) { // fall back: adapter already bound; skip restore or recreate adapter }","preventionTips":["Restore state immediately after creating the adapter, before setAdapter.","Create a fresh adapter instance when restoring late.","Follow Epoxy's saveState/restoreState ordering on EpoxyRecyclerView."],"tags":["android","epoxy","state-restoration","lifecycle-order"],"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"}