Use default back button behavior with android navigation component - android

I am using a navigation graph in my app. I have a bottom navigation bar. I am staring my navigation components with the following in my Main Activity:
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_artist_list, R.id.navigation_dashboard, R.id.navigation_notifications))
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
This starts my navigation components, which inflate the fragement setup in R.id.navigation_artist_list.
From an observer in that fragment I am navigating to another fragment with:
viewModel.selectedAlbum.observe(this, Observer { artist ->
val action = HomeFragmentDirections.actionNavigationHomeToAlbumFragment(artist)
root.findNavController().navigate(action)
})
However, none of the mechanisms available for navigation work properly. The back button just refreshes the fragment and populates the list in the fragment again. The back button on the action bar is completely ignored.
Do I need to do something else to have the proper back button behavior, to go back to the previous fragment?

In yout MainActivity, override the onSupportNavigateUp() method to call navigateUp() in the navigation controller
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host_fragment)
return navController.navigateUp()
}

Related

androidx navigation api: can we make the menu button always visible?

I have an app that uses the androidx navigation api. it works as expected. but i don't understand why the menu button would change from the hamburger icon to the up arrow, since we have a global one available to us. So, I was wondering if it was possible to force the api to keep the hamburger button at all times. I have found that if you use the following code;
val navSet = setOf(R.id.nav_journal, R.id.nav_copyright)
val drawerLayout: DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(navSet, drawerLayout)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
setupActionBarWithNavController(navController, appBarConfiguration)
then the hamburger icon will appear for both activities but it seems unwieldy to maintain a list of possible navigation activities.
Try this:
navController.addOnDestinationChangedListener { controller, destination, _ ->
if (destination.id == R.id.xxx || destination.id == R.id.xxx || ... ) {
supportActionBar?.setHomeAsUpIndicator(R.drawable.hamburger)
}
}
Find Hamburger icon here: https://materialdesignicons.com/
And add this to each of your other Activity or Fragment:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
requireActivity().drawerLayout.open()
}
return true
}
Try to use setOpenableLayout() to attach the drawerLayout into the appBarConfiguration
This requires to use AppBarConfiguration.Builder pattern:
val appBarConfiguration = AppBarConfiguration.Builder(
R.id.nav_journal, R.id.nav_copyright
).setOpenableLayout(drawerLayout).build()
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController: NavController = navHostFragment.navController
setupActionBarWithNavController(this, navController, appBarConfiguration);
Also make sure you add all the fragments that you don't want to show the up/back button in, into the appBarConfiguration. You already added a couple of them (R.id.nav_journal, R.id.nav_copyright), if you have more fragments in the drawer, then you need to add them as well.

Drawer layout with navigation component - Listener on only one item

I've got an issue concerning the implementation of a Drawer Layout with Navigation component.
I have created the drawer layout using the include Navigation Drawer Activity of Android Studio.
Actually, all is fine if the menu items purpose is to change fragments or activity (like programs, songs, settings etc ... on the screenshot) define in the navigation XML
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_user_programs_list,
R.id.nav_user_songs_list,
R.id.nav_user_settings,
R.id.nav_user_legal_notices,
R.id.nav_games
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
However, I also would like to execute a logout action on the "logout" menu item without launching another fragment or activity :
I managed to do it like that :
navView.setNavigationItemSelectedListener {
if (it.itemId == R.id.nav_logout) {
logoutUser()
}
true
}
But my problem is : With that method, all the other items which used to work (changing fragment) don't work anymore because it called the NavigationItemSelectedListener which do nothing in that case.
Is there a solution to combine the both method ? :
Changing fragment with the default drawer layout of android studio
Using a NavigationItemSelectedListener to only execute an action on only one menuitem.
I hope it's clear enough. Don't hesitate if you need precisions.
Thank you very much.
Solution
Ok, I figured it out, this is what the framework is calling for you:
NavigationUI.onNavDestinationSelected(dest, navController)
So you can do the same for all other cases:
navView.setNavigationItemSelectedListener {dest ->
when(dest.itemId) {
R.id.logout -> logout()
else -> NavigationUI.onNavDestinationSelected(dest, navController)
}
true
}
Update
The above stops "automatically closing" the drawer, so..
navView.setNavigationItemSelectedListener {dest ->
when(dest.itemId) {
else -> {
NavigationUI.onNavDestinationSelected(dest, navController)
drawerLayout.closeDrawers()
}
}
true
}

Using Android navigation component with single activiy, layout drawer and toolbars inside fragments

Is it possible to use Android Navigation component/graph in the single activity app in which each fragment has its own toolbar?
Also, container activity has a navigation drawer that needs setup with toolbar and navigation controller, but at the time of activity creation I don't have yet the toolbar.
I am using this code (called in onCreate)
private fun setupNavigation() {
// val toolbar = findViewById<Toolbar>(R.id.toolbar);
// setSupportActionBar(toolbar);
// supportActionBar!!.setDisplayHomeAsUpEnabled(true);
// supportActionBar!!.setDisplayShowHomeEnabled(true);
val drawerLayout = findViewById<DrawerLayout>(R.id.drawer_layout);
val navigationView = findViewById<NavigationView>(R.id.drawer_navigation_view);
val navController = Navigation.findNavController(this, R.id.navigation_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
NavigationUI.setupWithNavController(navigationView, navController)
navigationView.setNavigationItemSelectedListener(this)
}
But since I don't have toolbar at the time it throws an error (ActionBar.setTitle called on a null object).
Is it possible to have this, or I need to drop the idea to use navigation component in this case?
The only requirement is that you call setupActionBarWithNavController after you call setSupportActionBar(). If you're doing that in your Fragment, then just call setupActionBarWithNavController directly after that.
For example, in your Fragment:
private fun onViewCreated(view: View, bundle: savedInstanceState) {
val toolbar = view.findViewById<Toolbar>(R.id.toolbar);
// Set the Toolbar as your activity's ActionBar
requireActivity().setSupportActionBar(toolbar);
// Find the activity's DrawerLayout
val drawerLayout = requireActivity().findViewById<DrawerLayout>(R.id.drawer_layout);
// Find this Fragment's NavController
val navController = NavHostFragment.findNavController(this);
// And set up the ActionBar
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
}
There's also a separate NavigationUI.setupWithNavController() method that takes a Toolbar. This would be appropriate if you aren't actually using any of the other ActionBar APIs.

button back in startDestination with navigation component

I need a second activity with a nav graph and have a return button in toolbar to the first activity that also contains a nav graph
In my second activity I have onSupportNavigateUp and setupActionBarWithNavController when entering the fragments if the arrow back button appears but in the activity no.
Try adding setHomeButtonEnabled and setDisplayHomeAsUpEnabled in both the activity and the fragment and if the button appears back, but when I enter some fragment in front and return to the fragment startDestination disappears the button back
I just need to keep the button back in the activity and solve my problem
You can do it by specifying a setFallbackOnNavigateUpListener:
private fun setupToolbar() {
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration =
AppBarConfiguration.Builder()
.setFallbackOnNavigateUpListener { onNavigateUp() }
.build()
dataBinding.toolbar.apply {
setupWithNavController(navController, appBarConfiguration)
}
}
And then do whatever you want in the Activity's:
override fun onNavigateUp(): Boolean {
finish()
return true
}
You can't, activity has their own toolbars and in your case they have two different NavControllers. So your second activity manage NavUp Button for his fragment and when start Destination fragment comes NavUpButton(Backbutton) disappear because it has no destination left behind. And if you programmatically show NavUp Button on start destination of that (2nd activity) and manage onClick and start first activity that always goes to Start destination of first activity's fragment because it has it's own Nav Controller.
Problem is that Navigation UI not works like that. The better approach is use only one activity with multiple fragments. And use any other approach to solve your problem within the same nav controller.
Add setHomeButtonEnabled func. to your returning action. If you are returning with button add it to onClick or with backPress, override backPress.
With this solve : You will set your button enable, when you try to return your startDestination.
I created an interface to show/hide up button from the nav host activity. Here is how the activity implements the interface methods to show/hide up button:
override fun showUpButton() {
val navController = this.findNavController(R.id.nav_host)
val listener = AppBarConfiguration.OnNavigateUpListener { navController.navigateUp() }
val abc = AppBarConfiguration.Builder().setFallbackOnNavigateUpListener(listener).build()
NavigationUI.setupActionBarWithNavController(this, navController, abc)
}
override fun hideUpButton() {
val navController = this.findNavController(R.id.nav_host)
NavigationUI.setupActionBarWithNavController(this, navController)
}
Here the method in the activity when up button pressed:
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host)
if(!navController.navigateUp()){ // When in start destination
onBackPressed()
}
return navController.navigateUp()
}
In a fragment can listen whenever back button (NOT up button) pressed:
private fun setupBackPress() {
requireActivity()
.onBackPressedDispatcher
.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
}

Android Navigation Component with Nav Drawer and Action Bar Up Navigation get out of sync

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.

Categories

Resources