How to handle back button when at the starting destination of the navigation component
P

8

17

I've started working with the new navigation component and I'm really digging it! I do have one issue though - How am I supposed to handle the back button when I'm at the starting destination of the graph?

This is the code I'm using now:

findNavController(this, R.id.my_nav_host_fragment)
                .navigateUp()

When I'm anywhere on my graph, it's working great, it send me back, but when I'm at the start of it - the app crashes since the backstack is empty.

This all makes sense to me, I'm just not sure how to handle it.

While I can check if the current fragment's ID is the same as the one that I know to be the root of the graph, I'm looking for a more elegant solution like some bool flag of wether or not the current location in the graph is the starting location or not.

Ideas?

Puffer answered 19/6, 2018 at 21:21 Comment(3)
Do you overriding "onBackPressed"?Ketubim
Yeah, the code sample I've added is placed in the onBackPressed overridePuffer
#50577856Hagerty
M
15

I had a similar scenario where I wanted to finish the activity when I was at the start destination and do a regular 'navigateUp' when I was further down the navigation graph. I solved this through a simple extension function:

fun NavController.navigateUpOrFinish(activity: AppCompatActivity): Boolean {
return if (navigateUp()) {
    true
} else {
    activity.finish()
    true
}

}

And then call it like:

override fun onSupportNavigateUp() = 
        findNavController(R.id.nav_fragment).navigateUpOrFinish(this)

However I was unable to use NavigationUI as this would hide the back arrow whenever I was at the start destination. So instead of:

NavigationUI.setupActionBarWithNavController(this, controller)

I manually controlled the home icon:

setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_navigation_back)
Madgemadhouse answered 17/10, 2018 at 20:23 Comment(1)
If my current activity with a NavController does have a ParentActivity in the Manifest. I only need to replace setupActionBarWithNavController with a simple setDisplayHomeAsUpEnabled. Anyway, this tip works for me. Thank you.Shaddock
B
8

Override onBackPressed in your activity and check if the current destination is the start destination or not.

Practically it looks like this:

@Override
public void onBackPressed() {
    if (Navigation.findNavController(this,R.id.nav_host_fragment)
            .getCurrentDestination().getId() == R.id.your_start_destination) {
        // handle back button the way you want here
        return;
    }
    super.onBackPressed();
}
Bulwerlytton answered 5/7, 2018 at 18:54 Comment(2)
With getCurrentDestination() can set any functionality to any fragment based on their ids :) ThanksFollower
This is the best solution. Thanks!Lapham
K
7

You shouldn't override "onBackPressed", you should override "onSupportNavigateUp" and put there

findNavController(this, R.id.my_nav_host_fragment)
            .navigateUp()

From the official documentation: You will also overwrite AppCompatActivity.onSupportNavigateUp() and call NavController.navigateUp

https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing

Ketubim answered 21/6, 2018 at 4:23 Comment(1)
The official documentation says that the app:defaultNavHost="true" attribute ensures that your NavHostFragment intercepts the system Back button. It is not obvious from the documentation that you have to overwrite the AppCompatActivity.onSupportNavigateUp() method in order to support the app toolbar's back button. Thanks.Fleshy
K
3

In Jetpack Navigation Component, if you want to perform some operation when fragment is poped then you need to override following functions.

  1. Add OnBackPressedCallback in fragment to run your special operation when back present in system navigation bar at bottom is pressed .

     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
    
         onBackPressedCallback = object : OnBackPressedCallback(true) {
             override fun handleOnBackPressed() {
                 //perform your operation and call navigateUp
                findNavController().navigateUp()
             }
         }
     requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
     }
    
  2. Add onOptionsItemMenu in fragment to handle back arrow press present at top left corner within the app.

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
    
         setHasOptionsMenu(true)
     }
    
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
       if (item.itemId == android.R.id.home) {
           //perform your operation and call navigateUp
           findNavController().navigateUp()
           return true
       }
       return super.onOptionsItemSelected(item)
    }
    
  3. If there is no special code to be run when back is pressed on host fragment then use onSupportNavigateUp in Activity.

     override fun onSupportNavigateUp(): Boolean {
       if (navController.navigateUp() == false){
         //navigateUp() returns false if there are no more fragments to pop
         onBackPressed()
       }
       return navController.navigateUp()
     }
    

Note that onSupportNavigateUp() is not called if the fragment contains onOptionsItemSelected()

Kangaroo answered 29/8, 2020 at 10:31 Comment(0)
A
1

As my back button works correctly, and using NavController.navigateUp() crashed on start destination back button. I have changed this code to something like this. Other possibility will be to just check if currentDestination == startDestination.id but I want to close Activity and go back to other Activity.

override fun onSupportNavigateUp() : Boolean {
        //return findNavController(R.id.wizard_nav_host_fragment).navigateUp()
        onBackPressed()
        return true
    }
Ashjian answered 2/8, 2018 at 11:25 Comment(0)
L
1
/** in your activity **/
private boolean doubleBackToExitPressedOnce = false;
        @RequiresApi(api = Build.VERSION_CODES.M)
        @Override
        public void onBackPressed() {
            int start = Navigation.findNavController(this, R.id.nav_host_fragment).getCurrentDestination().getId();
            if (start == R.id.nav_home) {
                if (doubleBackToExitPressedOnce) {
                    super.onBackPressed();
                    return;
                }
                this.doubleBackToExitPressedOnce = true;
                Toast.makeText(MainActivity.this, "Press back again to exits", Toast.LENGTH_SHORT).show();

                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        doubleBackToExitPressedOnce = false;
                    }
                }, 2000);
            } else {
                super.onBackPressed();
            }
        }
Lesser answered 7/5, 2020 at 20:50 Comment(0)
A
0

If you mean the start of your "root" navigation graph (just incase you have nested navigation graphs) then you shouldn't be showing an up button at all, at least according to the navigation principles.

Airport answered 19/6, 2018 at 21:59 Comment(1)
The visual button isn't displayed - it's changed with the hamburger menu. I'm referring to the physical back button. How do I handle it?Puffer
C
0

Just call this in your back button Onclick

 requireActivity().finish()
Cerussite answered 29/6, 2020 at 18:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.