I'm using Jetpack Navigation to handle the bottom navigation controller. It works great; however, I am looking for a way to improve the UX.
Is it possible to disable the tab that the user is currently navigated to?
I'm able to "spam" the current tab in the bottom navigation, which reloads data & the UI unnecessarily. If this behavior (of re-tapping the current tab) is intended, is there a way to retain the state of the fragment, so that when navigated away & to, it doesn't recreate?
How I handle the bottom navigation:
navController = NavHostFragment.findNavController(navigation_host_fragment)
NavigationUI.setupWithNavController(bottom_navigation_view, navController)
As per this issue:
Feel free to set a OnNavigationItemReselectedListener, which takes precedence over the OnNavigationItemSelectedListener set by NavigationUI: setOnNavigationItemReselectedListener
bottom_navigation_view.setOnNavigationItemReselectedListener {
// Do nothing
}
Related
I have a feature as a module which consist of 3 fragments.
After User navigates to the 3'd fragment, there is a computation, when computation succeeds -> User should be navigated to the Home screen which is in the parent nav graph.
I've tried a lot of approaches, but nothing seem to work correctly, is there a way to finish current navigation graph and return to the parent graph?
Things I've tried:
Navigation to the home with a help of deeplink. Not a bad option, but it messes with backStack, and it is not user friendly in case feature will be exposed to different clients.
Navigating to the start destination with cleaning of the backstack and after navigateUp. Better option than the first, but still...
I have the following navigation xml on a BottomNavigationView, however when the user navigates inside the 1st fragment and then navigates to another fragment using the BottomNavigationView and then comes back to the initial fragment, it resets all the way to the default 1st fragment the navigation to a deeper fragment does not persist, i assume this is because the fragment gets recreated and from what i've looked at on other questions is that there is a way to save the data to re-set any text values on text fields or similar stuff, however i haven't seen if theres a way to avoid the fragment from being destroyed and re-created in order to make the navigation and whatever changes the user had previously made to the view persist.
Is there a way to do that, or should i use a savedInstance in such a way that it re-navigates to the previously navigated fragment?
Part of Navigation 2.4.0 is support for multiple back stacks - i.e., the ability for each tab of a bottom navigation bar to save its state, restoring that state when you reselect that tab.
As per that blog post:
If you’re using NavigationUI, our set of opinionated helpers for connecting your NavController to Material view components, you’ll find that multiple back stacks is enabled by default for menu items, BottomNavigationView (and now NavigationRailView!), and NavigationView. This means that the common combination of using navigation-fragment and navigation-ui will just work.
This means that if you are using the setupWithNavController methods, then upgrading to Navigation 2.4 will give you support for multiple back stacks immediately. You can verify that by going to your order fragment (thus building a back stack on that first tab), going to a different tab, then tap on the first tab again to reselect it.
Of course, it is your fragments state, not the instances themselves that are saved and restored. This means that each individual fragment must still save its state properly.
I have a BottomNavigationView setup with Navigation Component, and I want to intercept the handling of changing destinations on pressing an item, and prevent it from changing destination on a certain condition.
For example: Showing a dialog if the user is navigating to a destination and he's not signed in.
How do I do that?
I can't seem to be finding a listener that could be called OnDestinationChange with some sort of boolean returned to inform the navigation component that it should go ahead with the navigation or not.
There is no way to do so using navigation component methods, but there is an easy workaround that doesn't break navigation flow.
on your bottom navigation view get the menu and set onItemClickListener and return true if you don't want the navigation component to receive the click hence no navigation happens
here is example
binding.bottomNav.menu.findItem(R.id.searchFragment)?.setOnMenuItemClickListener {
if(viewModel.isUserAuthorized()) {
false // here this will allow navigation component to consume the click
}
else {
showUnauthorizedUserDialog()
true // here this will prevent navigation component from consuming the click
}
}
additionally according to docs of onMenuItemClick
#return Return true to consume this click and prevent others from
executing.
I have created a small sample app to test out the android navigation library. The general idea is that I want two tabs with their own navigation graph.
My main activity layout contains a BottomNavigationView with two NavHosts.
The ButtonNavigationView click listener takes care of showing one NavHost or the other and also calling NavigationUI.setupActionBarWithNavController with the selected controller to update the toolbar accordingly. Up navigation is working fine.
The problem I am facing right now is the back button.
There is a property app:defaultNavHost="true" which ensures that your NavHostFragment intercepts the system back button, but I want that to be on/off depending on the active graph. I couldn't find a way to change it :(
I know I can override onBackPressed() but I am trying to find a way for the library to do that work for me letting it know which graph is active.
As per the NavHostFragment source, all that app:defaultNavHost="true" does is call through to setPrimaryNavigationFragment(), which is how FragmentManager knows which child Fragment to send back button events to.
Therefore, when switching to a new Fragment, you can add setPrimaryNavigationFragment() to your FragmentTransaction to get the same behavior.
Im making navigation in my app using navigation component from jetpack.
I have 3 screens in my app, all of them implemented via Fragment:
- search screen
- list screen
- detail screen
When user presses search (on search screen), app navigates to list screen loads results and displays them. User selects one of the results and navigates to detail screen. If there is only one result, app navigates from list screen to detail screen automatically, effectively skipping list screen.
The problem is back navigation: when there was multiple results I need to navigate back to list screen, but if there was only one result I need to navigate back to search screen. I want just call navigateUp, but this will take me to list screen (in all cases) and then forward to detail screen if there is only one result.
When using FragmentTransaction directly, we can replace current fragment with and call addToBackStack only if we want to return to it later.
When using navigation component we can just navigate, and it behaves as replace + addToBackStack.
How can I achieve replace-without-adding-to-backstack behavior using architecture component?
Finally, I managed how to achieve it.
Now I have two actions with the same start and destination, their only difference is popUpTo attribute.
<action
android:id="#+id/action_listFragment_to_detailFragement"
app:destination="#id/detailFragment"/>
<action
android:id="#+id/action_listFragment_to_detailFragement_pop"
app:destination="#id/detailFragment"
app:popUpTo="#id/listFragment"
app:popUpToInclusive="true" />
action_listFragment_to_detailFragement is used when user manually performes transition and it should return to list screen later
action_listFragment_to_detailFragement_pop is used when transition performed automatically and list screen should not be shown after return.
In Navigation Component, you can use the method NavController.popBackStack(int action) which works similar to addToBackStack(null).
Just use { Safe-Args Directions } instead of ID:
override fun navigateTo(navController: NavController) {
val action =
FirstFragmentDirections.actionFirstFragmentToSecondFragment()
navController.navigate(action)
}