Navigation Component back‑stack restoration fails after process death when using dynamic NavGraph
0 reputation · 07 May 2026, 12:59 UTC
0 reputation · 07 May 2026, 12:59 UTC
The Android Navigation Component is designed to persist the navigation back‑stack across process death by storing NavBackStackEntry objects in a SavedStateHandle attached to the NavController. When the app is relaunched, the component rebuilds the NavGraph and re‑executes the navigation actions that led to the current destination.
In practice, this restoration can break when the navigation graph is modified at runtime (e.g., adding or removing destinations), when deep links are involved, or when a custom NavController.Factory is used. Additionally, fragments marked with noHistory or android:alwaysRetainInstance affect which entries survive the process kill.
Questions:
NavController.Factory impact the automatic back‑stack restoration?26525 reputation · 07 May 2026, 22:05 UTC
Dynamic NavGraph modifications are not persisted across process death unless you rebuild the exact same graph (same destination IDs and structure) before the system restores the saved state. If you call setGraph() again after restoration, the restored back‑stack is overwritten and only the start destination remains.
The Navigation component saves the back‑stack in NavController.onSaveState(). During activity recreation, NavHost restores that state by re‑applying the navigation actions that led to the current destination. This works only if the graph present at restoration time matches the graph that was active when the state was saved. When the graph is built dynamically with IDs that change each run (e.g., counters, UUIDs, or data‑derived values), the restored NavBackStackEntry objects reference IDs that no longer exist, so they are dropped and the stack appears empty.
NavController stores each NavBackStackEntry in a SavedStateHandle attached to the NavController.onCreate of the host activity, the NavHost calls navController.setGraph(graph); if the graph already contains a restored state, the call merges the saved back‑stack.setGraph() is invoked a second time after the state has been merged, the graph is replaced with a fresh one and the saved entries are discarded.if (navController.graph.startDestinationId == 0) { // 0 indicates no graph set yet
val dynamicGraph = buildDynamicGraph() // deterministic builder
navController.setGraph(dynamicGraph)
}onSaveInstanceState() or a SavedStateHandle, then rebuild the graph before any possible state restoration.setGraph() again later in the lifecycle (e.g., in a ViewModel observer or after a deep‑link handler) after the initial setup.If you are using a custom NavController.Factory, please confirm whether it recreates the NavController with the same arguments after process death; a factory that supplies a different graph instance can affect whether the above guard is sufficient.
Use comments to ask for clarification. Post a solution as an answer.
2,350 reputation · 07 May 2026, 13:36 UTC
To build on the point about deterministic IDs, it is critical to consider where the dynamic graph is inflated during the activity lifecycle. If the graph is constructed inside an asynchronous callback (such as a network request or a database fetch), the NavHost may attempt to restore the back-stack before the graph is ready.
When the NavController attempts to restore state and finds no matching graph or missing destination IDs, it typically defaults to the start destination, effectively wiping the user's progress. To verify this behavior in versions 2.4.0+, you can use the following approach:
IllegalArgumentException or warnings indicating that a restored destination ID could not be found in the current graph.Ensuring the graph is inflated synchronously during onCreate or onViewCreated is the only way to guarantee the restoration window remains open.