Joseph Hawkes-Cates
05/02/2023, 4:19 PMhiltViewModel()
and compose navigation to inject the VMs for these screens within the scope of what’s on the backstack. I’m seeing that these destinations whose state is saved do not call onCleared() on the VM when they are popped off the backstack. Has anyone else seen this and is it expected? 🧵Ian Lake
05/02/2023, 6:28 PMa new instance of the VM is created with the state restored from the previous instanceThese seem like opposites. Either the same VM instance is being reused (the expected behavior) or you get a brand new VM with no old state.
Joseph Hawkes-Cates
05/02/2023, 6:29 PMIan Lake
05/02/2023, 6:33 PMsaveState
to your popUpTo
call), those VMs are going to still exist in memory (that's intentional, it is part of the state of those destinations). That's why all of our guides avoid collecting in the VM itself, but expose a Flow to the UI that stops collecting when the UI goes away (e.g., using WhileSubscribed
on a stateIn
call).
The restoreState
call as part of navigate
is what brings those VMs back into the back stack. If you never restoreState
, then yeah, those VMs are going to be there until you donavBackStackEntry.id
to get the unique ID associated with each entry - that's the unique ID the VMs are associated with. If you aren't getting the same ID back, then these are new instances of the same destination, not a restored oneJoseph Hawkes-Cates
05/02/2023, 6:36 PMIan Lake
05/02/2023, 6:43 PMJoseph Hawkes-Cates
05/02/2023, 6:48 PMRootNavGraph > NavGraphA > Start-A
to RootNavGraph > NavGraphB > Start-B
when switching between destinations in the bottom bar. So A and B are different items in the bottom bar. That navigation between items always looks like this:
navController.navigate(routeA) {
launchSingleTop = true
restoreState = true
popUpTo(RootNavGraphName) {
saveState = true
}
}
The destination I’m looking at doesn’t have any nav arguments that would make the route different.Ian Lake
05/02/2023, 7:26 PMpopUpTo(navController.graph.findStartDestination().id)
?Joseph Hawkes-Cates
05/02/2023, 7:44 PMbackStackMap
// Starts at line 1695 in navigation 2.5.3
if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
navigated = restoreStateInternal(node.id, finalArgs, navOptions, navigatorExtras)
} else {
// ...
}
Then I looked at where the state is saved in that map:
// Starts at line 585 in navigation 2.5.3, inside popBackStackInternal
if (savedState.isNotEmpty()) {
val firstState = savedState.first()
// Whether is is inclusive or not, we need to map the
// saved state to the destination that was popped
// as well as its parents (if it is the start destination)
val firstStateDestination = findDestination(firstState.destinationId)
generateSequence(firstStateDestination) { destination ->
if (destination.parent?.startDestinationId == destination.id) {
destination.parent
} else {
null
}
}.takeWhile { destination ->
// Only add the state if it doesn't already exist
!backStackMap.containsKey(destination.id)
}.forEach { destination ->
backStackMap[destination.id] = firstState.id
}
// And finally, store the actual state itself
backStackStates[firstState.id] = savedState
}
This section is called, but savedState.first()
is always the intermediary nav graph for my destination rather than the screen destination itself. This is being set in backStateMap
, but the other state in savedState which is for my destination that I try to navigate to does not appear to be used.Ian Lake
05/02/2023, 9:18 PMJoseph Hawkes-Cates
05/02/2023, 9:18 PMIan Lake
05/02/2023, 9:22 PM