In Navigation3, how can I restore state with Saved...
# compose
p
In Navigation3, how can I restore state with SavedStateHandle (or similar), when navigating forward (after back).
In Navigation2, I think there was something like following:
Copy code
navController.navigate {
    popUpTo("X") {
        saveState = true
    }
}
e
In Nav3 you control your stack so I would imagine you would need a specialized structure to support this case. My initial thought is that you would need a pointer (index) in addition to the list of keys to define your back stack. That way navigating "forward" or "backward" should not remove entries from your stack, only change the pointer. This way you wont need to explicitly save / restore state when doing forward or backward navigation, the state will be maintained while that entry is on the stack (whether its forward in history or backward in history) For completeness, you can also have a
pop
operation that clears the current (-ly indexed) screen along with any forward history from that point.
p
I'm guessing I need something in the lines of a custom
NavEntryDecorator
e
No, not really. As long as the keys remain on the stack you pass into
rememberDecoratedNavEntries
(using the strategy I mentioned above for instance), their saved state will be managed by the default SavedStateDecorator
s
I don't know if keeping the entry still in your backstack might work, I am not sure how exactly one would do that. I particularly also don't know how exactly you'd keep many things in your backstack, yet still have your app properly handle exiting the app when you are in your "home" screen and you press back again. I probably didn't grasp Efeturi's suggestion here very well. So will suggest a different approach which we're also doing which you might be able to grab some inspiration from But we do do something similar as we save the state of our "saved" nested top-level backstacks when we navigate using the navigation rail/bar so we keep those entries' state saved while not having them on the backstack at all anymore. You need to do some custom NavEntryDecorator which does not just pop the saved state on navigating back which the default one does. This is the default one https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigation3/navigation3-runtime/src/commonMain/kotlin/androidx/navigation3/runtime/SaveableStateHolderNavEntryDecorator.kt;l=51?q=SaveableStateHolderNavEntryDecorator&sq=&ss=androidx%2Fplatform%2Fframeworks%2Fsupport which just saves and removes the state from the
SaveableStateHolder
Here is our custom one https://github.com/HedvigInsurance/android/blob/f632b03001a2946caad772dbf1bb02a2ea3e42cf/app/navigation/navigation-compose/src/androidMain/kotlin/com/hedvig/android/navigation/compose/RetainedSaveableStateHolderNavEntryDecorator.kt#L29-L69 which does not remove the state from some entries conditionally. Similarly, you can adjust your own decorator and add your own sort of tracking to conditionally not remove the state from items that leave your backstack, depending on your requirements.
e
> I don't know if keeping the entry still in your backstack might work, I am not sure how exactly one would do that. Any entries that make it into
rememberDecoratedNavEntries
will be a target for any NavDecorators. You can then additionally filter out any entries you dont want as part of your scene state or scene calculation. Below is my example of screens in bottom tab navigation.
s
Ah interesting, that feels like it should work too
Navigating back to an entry would mean you'd have to make it not hidden also right? How does that work?
e
> Navigating back to an entry would mean you'd have to make it not hidden also right? How does that work? Anything that should be navigated back to MUST be in the final list so such an entry will not be marked as hidden in the first place. . . . The piece of code above is specifically my implementation of tabbed navigation so the rules are different but I'll try to visualize: square brackets is a group of tabs, capitalized letter means currently visible screen
Copy code
(open app)

[A,b,c,d]         --------backstack-------- A

(open screen B)

[a,B,c,d]         --------backstack-------- A,B

(open screen D)

[a,b,c,D]         --------backstack-------- A,D

(open screen E)

[a,b,c,d] -> E    --------backstack-------- A,D,E
In the example, the app starts with tabbed screen with options A,B,C,D and A (which is the home screen) as visible screen. When creating the entries to pass to
rememberDecoratedNavEntries()
, I mark everything except the currently visible screen & the home screen as "_hidden"_. For step 1 ----- B, C & D are marked as hidden because A is both the current visible and the home. I then pass A,B,C,D to
rememberDecoratedNavEntries()
meaning all keys are decorated and their state will be saved/restored. And after filtering, only A will remain on the final list. For step 2 ----- when user navigates to B, A is home screen & B is currently visible, so only C & D are marked as hidden. I still pass A,B,C,D to
rememberDecoratedNavEntries()
meaning the state for all keys is still maintained. this time, A, B will be in the final list. For step 3 ----- same thing but this time B & C are marked as hidden so that the final list after filtering is A,D. Because all entries are passed to
rememberDecoratedNavEntries()
, again their saved state is maintained. For step 4 ----- when user navigates to a key that is not part of the tabs, that creates a new node. My data structure is a list of "_nodes"_ where each node is either a tabbed node OR a standalone node. In this final case, I'll have a list of 2 nodes: • Tabbed{A,B,C,D} • Standalone{E} I flat map these nodes to get the list of keys passed to
rememberDecoratedNavEntries()
(result: A,B,C,D,E), internally during this transform, I am marking irrelevant-to-final-backstack entries as hidden and then filter the resulting entries to remove hidden entries (result: A,D,E) so that what goes to my scene state is already the proper size and in order