kotturi
05/26/2026, 2:58 AMNav3 question about replicating singleInstance + onNewIntent behavior inside an entryProvider.
The goal: a destination should only ever exist once in the back stack, and when something tries to navigate to it again with new data, the already-rendered screen should receive that updated data (similar to how onNewIntent works for a singleInstance Activity).
My current approach:
- A NavParentViewModel (scoped above the NavDisplay) holds a Map<NavKey, Channel<T>>, one Channel per entry type
- When navigating to a destination that's already in the back stack, instead of pushing a new key, I send the new data into that key's Channel
- The composable for that entry collects from its Channel and updates its state accordingly
Is this a reasonable pattern for Nav3, or is there a recommended/idiomatic way to handle this? Would love to know if there's something cleaner, maybe using the NavEntry metadata or a different state-sharing mechanism, before I commit to this approach. Thanks!Ian Lake
05/26/2026, 3:12 AMIan Lake
05/26/2026, 3:15 AMcontentKey of your entry - all of the state is tied to the contentKey, so as long as that is the same, you'll get the exact same state, ViewModels, etc. and just magically get the new key instance itself, no Channel or anything like that requiredIan Lake
05/26/2026, 3:17 AMsingleInstance kind of mechanics in the first place: that almost always means you are making bad assumptions, IMOIan Lake
05/26/2026, 3:18 AMkotturi
05/26/2026, 3:46 AMapp-nav-graph at the root, with a home-nav-graph nested inside it that drives a home screen with a bottom nav bar and multiple nested screens. Some of those screens contain links that deep-link back into the home-nav-graph itself. I have a centralized DeepLinkResolver that constructs the route and triggers navigation β and when the target is home-nav-graph, I want the existing instance to stay alive and react to the new route info rather than pushing a fresh instance with a new ViewModel.
I did look at contentKey as a solution β keep it constant so the ViewModel survives, and pass the updated route params to the screen and to it's view-model via LaunchedEffect(param). But this creates a consume-once problem: if I don't clear the params after handling them, every time the user leaves home-nav-graph and comes back, LaunchedEffect(param) re-fires with the stale deep link data and triggers unintended navigation again. So I'd need to explicitly null out the param in the ViewModel after consumption, which adds boilerplate and makes state harder to reason about. That's the reason behind my ChannelFlow idea I am just exploring if this would be a better alternative.
Let me know if I made any mistake in my approach.Ian Lake
05/26/2026, 3:52 AMkotturi
05/26/2026, 3:58 AMNav2. In Nav3 terms, what I mean is a nested NavDisplay with its own back stack, scoped to the home screen. The setup is complicated because of the my product managers requirement π and making things backend driven.kotturi
05/26/2026, 4:03 AMDeeplinkResolver, and sometimes they target a section of the nested NavDisplay that's already on the back stack, which is where the "keep the existing instance alive and update it" problem comes from.Ian Lake
05/26/2026, 4:10 AMIan Lake
05/26/2026, 4:12 AM+ together even if you have one NavDisplay)kotturi
05/26/2026, 4:23 AMNavDisplay's because of migration and it let the team work more easliy on different sections in parallel. I had read the multiple back stacks recipe but not the deep link one, I'll check that out next.
Even with a single NavDisplay though, the core question remains: when a deep link targets something already on the back stack, swapping the key creates a new ViewModel. Using a constant contentKey preserves it but leaves me with the stale param problem. Curious to see how the deep link recipe handles that.Ian Lake
05/26/2026, 4:25 AMWhat does "receive that updated data" mean? Generally, there's a 1:1 relationship between the data you receive in onNewIntent and the data class representing a screen, so, by definition, that exact instance either already has the correct state or it is a brand new instance that isn't on the back stack already?
kotturi
05/26/2026, 5:26 AMNavDisplays and per-tab back stacks inside one of my NavDisplays, which is what's breaking the 1:1 for me, in a flat back stack your framing is exactly right.
The distinction I was trying to draw is in how the new data arrives in my setup:
Activity:
Screen A (ViewModel survives cause of singleInstance)
onCreate() β Intent
onNewIntent() β new Intent // delivered as a one-shot callback
Compose / Nav3:
Screen A (ViewModel survives via stable contentKey)
first render β HomeScreen(route = "tab_one")
re-delivery β HomeScreen(route = "tab_two")
In the Activity case, onNewIntent is a callback naturally event-shaped, consumed and gone. In the Compose case, if I couple the param to the route data class, it lingers on the backStack entry forever after I've acted on it β the route now carries a stale instruction. Any later recomposition (config change, a sibling backStack mutation, anything that replays the route into the screen) either re-applies the stale command or forces me to remember to clear it explicitly. A channel above the entry lets me consume each re-delivery as an event instead of persisting it on the route.
note: this is for my setup in particular where I have multiple NavDisplays and one of the NavDisplay having multi-backstack handling.Ian Lake
05/26/2026, 1:37 PMIan Lake
05/26/2026, 1:39 PMPablichjenkov
05/27/2026, 4:12 AMkotturi
05/27/2026, 7:41 AMResultEventBus fits well for my current setup and use-case. As for moving to a single NavDisplay given the scoped overlays, bottomsheet's and per-bottomNav-tab back stacks, it may introduce more complexity than it solves for my app specifically, have to explore more before moving to single NavDisplay .