Reputation: 693
Starting with version 2.4.0-alpha01, the NavigationUI helpers support multiple back stacks without any code change. If your app uses the setupWithNavController() methods for BottomNavigationView or NavigationView, all you need to do is to update the dependencies and multiple back stack support will be enabled by default.
In my aplication I have "Intro" screen.(TestFragment)
When user clicks on the button, app is navigating to home screen where Bottom navigation is present.
When navigating from "Intro screen" I want to clear back stack.
<fragment
android:id="@+id/testFragment"
android:name="com.example.android.navigationadvancedsample.TestFragment"
android:label="Test" >
<action
app:popUpToInclusive="true"
app:popUpTo="@id/testFragment"
android:id="@+id/action_test_to_title"
app:destination="@id/titleScreen" />
</fragment>
In this situation multiple back stack funcionality is not working. There is just one back stack. However when property popUpToInclusive is set to false multiple back stack are present and everything is working as expected!
bottom_nav.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/titleScreen"
android:icon="@drawable/ic_home"
android:contentDescription="@string/cd_home"
android:title="@string/title_home" />
<item
android:id="@+id/leaderboard"
android:icon="@drawable/ic_list"
android:contentDescription="@string/cd_list"
android:title="@string/title_list" />
<item
android:id="@+id/register"
android:icon="@drawable/ic_feedback"
android:contentDescription="@string/cd_form"
android:title="@string/title_register" />
</menu>
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.android.navigationadvancedsample.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav"/>
</LinearLayout>
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager.findFragmentById(
R.id.nav_host_container
) as NavHostFragment
navController = navHostFragment.navController
// Setup the bottom navigation view with navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)
// Setup the ActionBar with navController and 3 top level destinations
appBarConfiguration = AppBarConfiguration(
setOf(R.id.titleScreen, R.id.leaderboard, R.id.register)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}
nav_graph.xml
<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/testFragment">
<fragment
android:id="@+id/titleScreen"
android:name="com.example.android.navigationadvancedsample.homescreen.Title"
android:label="@string/title_home">
<action
android:id="@+id/action_title_to_about"
app:destination="@id/aboutScreen"/>
</fragment>
<fragment
android:id="@+id/aboutScreen"
android:name="com.example.android.navigationadvancedsample.homescreen.About"
android:label="@string/title_about" />
<fragment
android:id="@+id/leaderboard"
android:name="com.example.android.navigationadvancedsample.listscreen.Leaderboard"
android:label="@string/title_list">
<action
android:id="@+id/action_leaderboard_to_userProfile"
app:destination="@id/userProfile"/>
</fragment>
<fragment
android:id="@+id/userProfile"
android:name="com.example.android.navigationadvancedsample.listscreen.UserProfile"
android:label="@string/title_detail">
<deepLink
android:id="@+id/deepLink"
app:uri="www.example.com/user/{userName}"
android:autoVerify="true"/>
<argument
android:name="userName"
app:argType="string"/>
</fragment>
<fragment
android:id="@+id/register"
android:name="com.example.android.navigationadvancedsample.formscreen.Register"
android:label="@string/title_register">
<action
android:id="@+id/action_register_to_registered"
app:destination="@id/registered"/>
</fragment>
<fragment
android:id="@+id/registered"
android:name="com.example.android.navigationadvancedsample.formscreen.Registered"
android:label="Registered" />
<fragment
android:id="@+id/testFragment"
android:name="com.example.android.navigationadvancedsample.TestFragment"
android:label="Test" >
<action
app:popUpToInclusive="false"
app:popUpTo="@id/testFragment"
android:id="@+id/action_test_to_title"
app:destination="@id/titleScreen" />
</fragment>
</navigation>
Why multiple back stack are not supported when navigating from another fragment and clearing back stack? Maybe this is a bug in the jetpack navigation library.
Upvotes: 2
Views: 3678
Reputation: 3099
The general advice seems to be "Don't pop the startDestination" but no one really explained why.
If you look at this comment in the NavController, you can see that it won't pop anything if it can't find the start destination in the backstack, hence your BottomNavigation Fragments end up stacking instead of popping.
// We were passed a destinationId that doesn't exist on our back stack. Better to ignore the popBackStack than accidentally popping the entire stack
You can get around this by overriding bottomNavigation.setOnItemSelectedListener
and implementing the navigation yourself:
setOnItemSelectedListener { item ->
val options = NavOptions.Builder().apply {
setLaunchSingleTop(true)
setRestoreState(true)
setEnterAnim(R.animator.nav_default_enter_anim)
setExitAnim(R.animator.nav_default_exit_anim)
setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
setPopExitAnim(R.animator.nav_default_pop_exit_anim)
setPopUpTo(
R.id.your_home_fragment,
inclusive = false,
saveState = true
)
}.build()
navController.navigate(item.itemId, null, options)
navController.currentDestination?.hierarchy?.any { it.id == item.itemId } == true
}
Where R.id.your_home_fragment
is the id of the first navigation items Fragment.
Upvotes: 0
Reputation: 617
I recently walkthrough to your code and found that in your nav_graph.xml you defined testFragment as startDestination, this is the start point of your application, so when you are on welcome screen that you have bottomNavigationView, when you click on the back button, it will return to testFragment Screen, if your app is single activity (single page application) you have to implement OnBackPressedCallback on the first fragment of your welcome screen, some sample code:
OnBackPressedCallback callback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
NavBackStackEntry backStackEntry =
getNavController().getPreviousBackStackEntry();
if (backStackEntry != null) {
NavDestination destination = backStackEntry.getDestination();
if (destination != null) {
if (destination.getId() == R.id.homeFragment) {
requireActivity().finish();
}
}
}
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(callback);
Upvotes: 0