I have 3 tabs A, B, C in BottomNavigationView and each has a nav graph.
I can do what I want perfectly in navigation 2.3.3 by a complicated navigation extension, just like the old architecture-components-samples. This sample is now upgrade to 2.4.0. which use less code.
What I want is:
Step: Graph A' s start destination fragment A1 navigate to A2.
Step: Tap tab B or C.
B or C' s navigateUp action is back to A. (works fine)
When back to tab A, it shows A2. (in 2.4.0' s sample, shows A1)
The BottomNavigationView' s ItemReselected action is popBackStack to current graph' s startDestination. (how to set setOnItemReselectedListener if the navController does not change?)
The three startDestination fragment A1, B1, C1 are top level destinations, so B1 and C1' s toolbar do not show back icon. (works fine, because the sample set a set of these 3 fragments instead of navController.graph to AppBarConfiguration)
2.4.0 said it support Multiple back stacks. What does it mean? Can I make my BottomNavigationView in 2.4.0?
Here is how I do "3. The BottomNavigationView' s ItemReselected action..." in 2.3.3:
private fun BottomNavigationView.setupItemReselected(
graphIdToTagMap: SparseArray<String>,
fragmentManager: FragmentManager
) {
setOnNavigationItemReselectedListener { item ->
... // get the item' s navController
navController.popBackStack(
navController.graph.startDestination, false
)
}
}
What I do in 2.4.0:
just copy the sample code.
Add setOnItemReselectedListener and popBackStack
import android.util.SparseIntArray
import androidx.core.util.getOrElse
private val startDestinationIdByNavId: SparseIntArray by lazy(NONE) {
SparseIntArray(5).apply {
put(R.id.tab_home_nav, R.id.tabHomeFragment)
put(R.id.tab_profile_nav, R.id.tabMyProfileFragment)
}
}
fun setupViews() {
binding.bottomNavView.run {
setupWithNavController(navController)
// Pop the back stack to the start destination of the current navController graph
setOnItemReselectedListener {
navController.popBackStack(
destinationId = startDestinationIdByNavId.getOrElse(it.itemId) {
error("Unknown menu item $it")
},
inclusive = false,
)
}
}
}
Related
I am trying to implement a way for the menuitem button to navigate to a different fragment based on the current fragment.
For example, if I am in the fragment_home, I can access the dog_fragment and from the dog_fragment I can access another fragment like cat_fragmentand vice versa.
So far I am only able to navigate from fragment_home to dog_fragment, however when I try to access any other fragments from the dog_fragment my app crash. If I try to access another fragment from any other fragment accept from the home_fragment my app crashes
//This is in the Mainactivity
fun onComposeAction(item: MenuItem) {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
val navController = navHostFragment.navController
when (item.getItemId()) {
R.id.Hamster -> {
navController.navigate(R.id.action_home2_to_hamster)
}
R.id.Dog -> {
navController.navigate(R.id.action_home2_to_dog)
}
R.id.Cat ->{
navController.navigate(R.id.action_home2_to_cat)
}
}
}
Simply tie the menuitems to the destination fragments you have.
I am using Accompanist Bottom Sheet Destinations in my project. The setup is exactly the same as shown in docs.
#Composable
fun MyApp() {
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator) {
NavHost(navController, Destinations.Home) {
composable(route = "home") {
HomeScreen(
openBottomSheet = { navController.navigate("sheet") }
)
}
bottomSheet(route = "sheet") {
MyBottonSheet(
navigateToSomeRoute = { navController.navigate("some_route") }
)
}
composable(route = "some_route") {
SomeComposable(
onBackPress = { navController.navigateUp() }
)
}
}
}
}
Here I have a button in MyBottomSheet which opens the SomeComposable screen. But when I navigateUp from there, I reach HomeScreen i.e. the bottom sheet was popped off the backstack when I navigated away from it. How can I keep my bottom sheet in the backstack so that when I press Back in SomeComposable I go back to the previous state with MyBottonSheet open (on top of HomeScreen)?
This is not possible. As per the Navigation and the back stack documentation:
Dialog destinations implement the FloatingWindow interface, indicating that they overlay other destinations on the back stack. As such, one or more FloatingWindow destinations can be present only on the top of the navigation back stack. Navigating to a destination that does not implement FloatingWindow automatically pops all FloatingWindow destinations off of the top of the stack. This ensures that the current destination is always fully visible above other destinations on the back stack.
Bottom sheet destinations are also FloatingWindow destinations that float above the other destinations. As such, it is expected that they are automatically popped off the back stack when you navigate to anything other than another dialog or bottomSheet destination.
I have a complicated structure where a number of Fragments link to each other, so using NavController I am trying to avoid creating multiple duplicates of the same Fragment in the BackStack.
I found this post How to check Navigation Destination is in the NavController back stack or not?, which I have implemented as below:
private fun onSiteItemClicked(item: SiteObject) {
Log.d(TAG, "onItemClicked() - ${item.siteReference}")
item.siteID.let {
businessViewModel.updateCurrentSiteVMLiveData(it)
try {
val backStackEntry : NavBackStackEntry = navController.getBackStackEntry(R.id.siteFragment)
makeToast("backStackEntry = ${backStackEntry.destination.label.toString()}")
// Navigate to existing Fragment!! HOW TO DO?
} catch (ex: IllegalArgumentException) {
navController.navigate(R.id.action_contactFragment_to_siteFragment)
// Creates new Fragment as one doesn't existing in backstack. THIS WORKS!
}
}
}
So I have a reference to the Fragment in the BackStack, but I can't see how to navigate to it..
I have an Activity with a navGraph that conditionally chooses a startDestination when the the Activity is created.
private suspend fun checkStatusAndNavigate(userID: Int) {
val navController = findNavController(nav_host_container)
val user = getUserFromDB(userID)
val dateNow = Date((if (prefSaved) prefTime() else System.currentTimeMillis())
val navGraph = navController.navInflater.inflate(nav_graph).apply {
// NavGraph inflates with one of three possible starting points: A, B, or C.
startDestination = when {
checkDate(dateNow) == DateCheck.OldDate -> R.id.fragment_a
!user.hasOptedIn -> R.id.fragment_b
else -> R.id.fragment_c
}
}
navController.setGraph(navGraph)
}
After navController.setGraph(navGraph), the startDestination is launched. This works as expected.
Fragment B's only destination out is to Fragment C. But when the user is in Fragment C and does onBackPressed(), I would like to skip Fragment B, and return to whatever called the Activity (there are a few options, so overriding onBackPressed isn't a great option).
I don't think this is a popTo or popToInclusive problem. I don't want to clear the backstack. I just want to keep Fragment B from ever entering the backstack if at all possible.
If you don't want B to be accessible on back from C, then you're supposed to create an Action that is popTo=B, popToInclusive=true, and destination=C.
I've been building a sample app with the new Navigation Architecture Component in combination with a Nav Drawer.
I have my navigation graph created, my fragments created, and the nav drawer displaying and navigating between the fragments mostly as expected. The problem is that each time I select an item from the nav drawer, it adds the fragment to the stack, instead of popping the existing one and adding the new one. This means that if I navigate to a new fragment, I've created a back stack and tapping the menu button in the action bar pops the latest fragment off the stack, instead of opening the nav drawer as I would expect. Here is my code:
private fun configureNavigation() {
navDrawerLayout = findViewById(R.id.navigation_drawer_layout)
navView = findViewById(R.id.navigationView)
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.workouts_fragment, R.id.create_workout_fragment, R.id.workout_history_fragment),
navDrawerLayout
)
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
NavigationUI.setupWithNavController(navView, navController)
navView.setNavigationItemSelectedListener(this)
}
override fun onSupportNavigateUp() = navController.navigateUp()
override fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
menuItem.isChecked = true
navDrawerLayout.closeDrawers()
#IdRes val destination: Int = when (menuItem.itemId) {
R.id.workouts_nav_drawer_item -> R.id.workouts_fragment
R.id.create_workout_nav_drawer_item -> R.id.create_workout_fragment
R.id.workout_history_nav_drawer_item -> R.id.workout_history_fragment
else -> {
throw IllegalArgumentException("Attempting to process an unrecognized menuItem id in the navigation drawer layout")
}
}
if (destination != currentDestination) {
currentDestination = destination
navController.navigate(destination)
}
return true
}
I discovered there were two requirements to get the nav drawer, nav graph, and action bar completely in sync with my desired behaviour.
The first is the AppBarConfiguration. I had to create an app bar configuration containing a set of top level destinations (the top level fragments in the nav drawer).
The second aspect was to make sure that in onSupportNavigateUp() function to include the app bar configuration in the call as such: `navController.navigateUp(appBarConfiguration).
Once I had done these two things, everything worked as expected, and the nav drawer, action bar, and up button all worked in sync without unnecessarily adding fragments to the stack.