Reputation: 6225
When using Jetpack Navigation, we can use popUpTo
and popInclusive
to clear the stack. But how do I clear the stack when I don't know what destination to popUpto
?
Say I have 3 main destinations that should have a clear stack when we arrive at them. And each main destination has its own flow of screens (which can be accessed directly with a deeplink).
Assuming the navigation flows downwards here:
Start app:
- Main Dest 1 (nav to dest 2)
- Dest 2 (nav to dest 3)
- Dest 3 (nav to main dest 2)
--clear--
- (with clear stack)
Main Dest 2 (nav to dest 4)
- Dest 4 (nav to main dest 3)
--clear-->
- (with clear stack)
Main Dest 3
- Back button should close the app here
Since the navigation is very dynamic, I cannot guarantee the root destination. Even though I could, for example, pop up to "Main Dest 2", I cannot know that it's on the stack since I might have gone directly to "dest 4" from a URL.
I there a way of knowing which destination is the lowest so I can pop up to it and clear the stack?
Upvotes: 1
Views: 9064
Reputation: 200130
The root of your graph is always on the back stack, so you can always popUpTo
that destination.
So just make sure your root <navigation>
element has an android:id
associated with it:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@+id/main_dest_1">
<!-- The rest of your graph -->
</navigation>
Then whenever you want to pop the entire graph, just popUpTo="@id/nav_graph"
.
Of course, as per the Principles of Navigation, you should not be popping the whole stack off when you go to Main Dest 2, Main Dest 3, etc. as it is extremely important from a UX perspective that users know when the back button will exit the app - that start destination is exactly that sign post for users to know when that'll happen and prevents accidental closure of the app when users expect to return to the first screen before exiting your app.
Upvotes: 0
Reputation: 1002
You can add a destination change listener in root or base fragment and listen for navigation changes.
NavController.OnDestinationChangedListener { controller, destination, arguments ->
// react on change or
//you can check destination.id or destination.label and act based on tha
)}
Upvotes: 0
Reputation: 62209
I couldn't find any official solution to the problem, but here's an approach that works for me:
private val mBackStackField by lazy {
val field = NavController::class.java.getDeclaredField("mBackStack")
field.isAccessible = true
field
}
fun popToRoot(navController: NavController) {
val arrayDeque = mBackStackField.get(navController) as java.util.ArrayDeque<NavBackStackEntry>
val graph = arrayDeque.first.destination as NavGraph
val rootDestinationId = graph.startDestination
val navOptions = NavOptions.Builder()
.setPopUpTo(rootDestinationId, false)
.build()
navController.navigate(rootDestinationId, null, navOptions)
}
Upvotes: 1
Reputation: 838
As far as I know there is no easy and beautiful way to get what you're trying to achieve. There are a few possible solutions though.
You could have a base-destination which is always at the bottom of your stack, let's call it baseDest
.
In your actions which need to clear the stack you could then always use
<action
android:id="..."
app:destination="@id/mainDestX"
app:popUpTo="@+id/baseDest"
app:popUpToInclusive="false" />
This will always keep your baseDest at the bottom of your stack allowing you to always popUpTo it. To achieve your behaviour on a backpress in one of your main destinations you could either:
onBackPressed
-method in all your main dests to close the app. (Could be easily done if all your main destionations extend from the same class)Maybe you should rethink how the navigation in your app works. Rather than having some sort of possibly-cyclic graph structure it's easier to deal with tree-like navigation structures (which are also also more intuitively understood by users). Of course this is not always an option, but you should keep this in mind and consider restructuring your navigation.
Manually implement all your actions and navigations and use the methods provided in NavController to manually remove all entries in your stack before navigating to one of your main destinations. One possible approch here to do this is to retrieve your nav controller by using the findNavController method in your fragment. I have not tested any of the following methods, but you could try to:
yourNavController.getGraph().clear()
followed by a yourNavController.navigate(yourDest)
saved = yourNavController.saveState()
and on navigation to one of your mainDestinations use yourNavController.restoreState(saved)
, again followed by a yourNavController.navigate(yourDest)
yourNavController.setGraph(createdGraph)
Upvotes: 2