Configure a production navigation host
Use this guide after completing the navigation tutorial. It turns the two-destination example into one host with explicit restoration, Back, ownership, and failure policy. For the runtime reasons behind these rules, see the navigation architecture. Exhaustive signatures and optional motion, deep-link, multi-stack, graph-owner, and adaptive-pane APIs remain in the Navigation Android module manual.
Choose one controller owner
Create the controller with rememberNavHostController in the same UI owner that mounts NavHost.
Never cache it in a process singleton or share it across hosts. It saves committed stack and owner
identities, arguments, and destination state through the nearest ViewCompose registry.
Mount NavHost below both LocalLifecycleOwner and LocalViewModelStoreOwner. Standard Activity
and Fragment setUiContent hosts do this; renderInto integrations must provide both because no
private fallback store is created.
Use a stable NavGraph when routes need typed arguments, nested ownership, or deep links. Restore
fails closed to the start destination when the graph rejects a saved hierarchy.
Declare one stable NavRouteSpec<T> per application route and reuse it in destination,
navigation, typed commands, and NavEntry.toRoute. Encode durable IDs and small primitives, load
domain objects in the ViewModel, and keep names and schemas restore-compatible. Codec errors occur
before the host transaction.
The optional Kotlinx adapter derives specs for flat serialized routes; unsupported shapes use explicit Core codecs.
Model external navigation with NavDeepLinkRequest. A declaration may constrain URI, action,
MIME type, or all three, and every constraint must match. Android maps only Intent data, action,
and type. Inspect NavDeepLinkResult, and validate the complete URI at security boundaries.
Restore state and connect platform Back
Leave systemBackEnabled = true. While STARTED and able to pop, NavHost uses the nearest
View-tree NavigationEvent owner, or Activity Back only when none exists. A root delegates outward;
do not add a second owner or callback around the host.
Call popBackStack for an in-UI Back action. System Back and predictive Back then use the same
transaction. Predictive preview never publishes its candidate: cancel restores the committed
scene, and completion follows the programmatic-pop path.
Declare a stable NavResultKey, pop with its value, and observe it with NavResultEffect in the
previous page. Delivery is saved, FIFO, and waits for RESUMED; explicit acknowledgement or retry
uses the destination-context inbox.
Keep route rendering exhaustive
Render every accepted route in the NavHost content block and reject unknown routes immediately.
It runs in the destination-owned lifecycle, stores, saveable namespace, and child render session;
repeated pushes create distinct owners unless launch mode reuses one. Scoped stores survive
configuration recreation with their parent, clear on permanent removal, and recreate ViewModels
from restored state after process death. Change contentKey only for a non-observable parent
capture; changing a host owner, controller, factory, debug identity, or host key recreates the
native host.
Observe destination presentation without duplicating Lifecycle
Read LocalNavDestinationContext.current during destination DSL declaration when content needs to
distinguish presentation roles, and capture that nearest holder for callbacks. Its entry survives
hidden-presentation disposal; permanent removal ends updates and destroys its Lifecycle. Active
resources still follow AndroidX Lifecycle because this context is coarse and has no frame progress.
Add modal destinations without a second lifecycle model
Pass a stable
NavSceneStrategies.trailingOverlays
strategy to NavHost. Its predicate selects only a trailing stack suffix; pane policy lays out the
prefix. Destination content draws its surface/scrim, while the host owns modal input and lifecycle.
Use ordinary Back, result, restore, and predictive APIs rather than parallel page owners.
Choose presentation retention deliberately
Keep the default NavPresentationRetentionPolicy.DisposeWhenHidden unless device evidence for a
destination proves rebuild cost unacceptable. It releases hidden native presentation but retains
owner state. Use measured Bounded(n) caching when needed; RetainAll is unbounded. Policy changes
preserve owners, and initial or restored attachment materializes only the current scene layout.
Keep one policy source in custom integrations
Normal applications use NavHost. A custom host must execute each complete NavExecutionPlan:
prepare before commit, publish scene/lifecycle/interaction/Back together, then clean up at the
planned boundary. It must not derive parallel lifecycle, retention, or Back policy.
Handle command outcomes
Every command returns Committed, NoChange, Queued, or Failed. Observe navigationState for
queued completion and report the structured failure without replacing the committed stack. Use
onFailure for logging, fallback, or tests; otherwise NavHostException is raised. Pre-commit
failure preserves the old page, and NavFailure.stackCommitted marks a post-commit boundary.
Verify the task
Run the compiled tutorial and the Navigation Android tests:
./gradlew :samples:tutorials:assembleDebug :viewcompose-navigation-android:testDebugUnitTest
On one real host, verify UI and system Back agree; Activity recreation preserves route, entry,
saveable state, and ViewModel identity; predictive Back cancel/commit changes the stack zero/one
times; a pre-commit render failure keeps the prior page; and deep-stack presentation stays within
policy while an evicted page restores its owner state. Detached commands, duplicate pops,
premature owner cleanup, unbounded native retention, or treating Queued as completion fail the
configuration.