Fix NavigationController crash from onNewIntent before Compose attaches#284
Merged
Conversation
Move popTo(Dashboard) out of MainActivity.onNewIntent and into a Compose-level LaunchedEffect that collects a new MainActivityVM.deeplinkAccepted SingleEventFlow. Use goTo with popUpTo=Dashboard for incoming-share so it does not race with the parallel popTo collector.
onNewIntent could fire before the setContent{} lambda registered the back stack with NavigationController (timing-sensitive race observed on Samsung Galaxy S24 Ultra running Android 16 Beta), causing IllegalStateException at NavigationController.kt:15 from popTo. Same canonical pattern as permission-pilot.
3 tasks
d4rken
added a commit
to d4rken-org/capod
that referenced
this pull request
May 4, 2026
Move consumeUpgradeExtra() out of MainActivity.onNewIntent and into a Compose-level LaunchedEffect that collects a new MainActivity.warmIntents SingleEventFlow.
onNewIntent could fire before the setContent{} lambda registered the back stack with NavigationController, leaving navCtrl.goTo(Nav.Main.Upgrade) to throw IllegalStateException("NavigationController not initialized"). Same race shipped a fix for in octi (d4rken-org/octi#284).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a Google Play crash report on Samsung Galaxy S24 Ultra running Android 16 Beta (SDK 36):
Root cause
NavigationController._backStackis registered viasetup(backStack)from inside thesetContent { }Compose lambda — asynchronous, frame-scheduled.MainActivity.onNewIntentruns synchronously and the stack trace shows it firing insideperformResumeActivity → deliverNewIntents. There is a window during activity recreation (config change, dark-mode flip) or singleTask warm intent delivery whereonNewIntentruns before the Compose lambda has executed._backStackis stillnull, andpopTo(Nav.Main.Dashboard)throws.Fix
Move the
popTo(Dashboard)step out ofonNewIntentand into a Compose-levelLaunchedEffect.onNewIntentbecomes a pure intent-parsing entry point that emits VM events; the navigation work happens once Compose has attached, where the back stack is guaranteed initialized.MainActivityVM.deeplinkAccepted: SingleEventFlow<Unit>— fired after a successful widget or file-share-notification deeplink parse (not for system-share intents)MainActivity.onNewIntentnow just callsvm.handleDeeplinkIntent(intent)and returnsLaunchedEffectinsidesetContent { }collectsdeeplinkAccepted→navCtrl.popTo(Nav.Main.Dashboard)incomingShareEventscollector switched togoTo(FileShareList, popUpTo = Dashboard)(single atomic call) so it does not race with the parallelpopTocollectorNavigationControlleris unchanged — stricterror()getter stays, contract not weakenedThis matches the canonical pattern already used in
permission-pilot(intent → VM event → Compose-levelLaunchedEffect→navCtrl.*).Test plan
./gradlew :app:testFossDebugUnitTestpasses (6 new VM tests coveringdeeplinkAcceptedacross widget / file-share / system-share / unrecognized / not-onboarded cases)./gradlew assembleFossDebugcompiles cleanly[Dashboard, FileShareList][Dashboard]and the module detail sheet opens