Skip to content

Load the graph for a nested NavigatorHost that replaces another instance - #220

Merged
mbarta merged 1 commit into
mainfrom
mb/nested-navigator-host-reregister
Oct 12, 2026
Merged

mbarta merged 1 commit into
mainfrom
mb/nested-navigator-host-reregister

Conversation

@mbarta

@mbarta mbarta commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

A fragment that hosts a nested NavigatorHost crashes when a new instance of the fragment replaces the old one before the old one is destroyed. This happens when a user leaves such a screen and quickly opens it again, for example while the old fragment's exit transition is still running. route() on the new host's navigator throws:

java.lang.IllegalStateException: You must call setGraph() before calling getGraph()
    at androidx.navigation.NavController.getGraph(NavController.kt:103)

Cause

#202 moved graph loading from NavigatorHost.onCreate() into HotwireActivityDelegate.registerNavigatorHost(). Registration is keyed by the host's view ID and skips any ID that's already registered. When the fragment gets replaced:

  1. The new nested host's onViewCreated() registers it. The old host still owns the ID, so the delegate skips the new host and it never loads its graph.
  2. The old host's onDestroy() unregisters it, which removes the ID entirely.
  3. The next route() on the new host's navigator reads NavController.graph and throws.

Before #202, onCreate() always loaded the graph, so step 1 didn't break anything.

Fix

  • registerNavigatorHost() lets a new host instance take over an ID. The same instance still can't register twice, so a recreated view doesn't add a second destination listener.
  • unregisterNavigatorHost() only removes the entry while the host still owns it.

The old host keeps its destination listener. That listener only fires for the old host's own NavController, which is on its way out.

Tests

HotwireActivityDelegateTest adds 2 instances of a fragment with a nested NavigatorHost under the same ID, the second one before the first is removed:

  • The new host loads its graph.
  • Removing the old fragment keeps the new host registered.

Both tests fail on main and pass with the fix. Reverting either half of the fix fails at least 1 of them. ./gradlew testRelease passes.

I also ran the repro in an app with a nested NavigatorHost on an emulator, with the animator duration scale at 10x to keep the old fragment alive long enough. With 1.3.1, both runs crashed. With this branch, 3 runs didn't crash, and the nested navigator loaded and navigated normally.

Since #202, a NavigatorHost loads its graph in
HotwireActivityDelegate.registerNavigatorHost(), which skips any host
whose view ID is already registered. A fragment that hosts a nested
NavigatorHost can be replaced by a new instance of itself, and the new
nested host registers before the old one is destroyed. The new host
never loads its graph, and when the old host unregisters it removes the
ID entirely. The next route() on the new host's navigator throws "You
must call setGraph() before calling getGraph()".

A new host instance now takes over the ID, and a host only removes the
entry while it still owns it.
@mbarta mbarta self-assigned this Oct 12, 2026
@mbarta
mbarta merged commit 6a1b907 into main Oct 12, 2026
1 check passed
@mbarta
mbarta deleted the mb/nested-navigator-host-reregister branch October 12, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant