30

I want to add custom up navigation from fragment using Navigation component

In my build.gradle(app) I use androidx.appcompat:appcompat:1.1.0-alpha04 dependency to have access to onBackPressedDispatcher from activity.

So I implemented OnBackPressedCallback in my fragment and registered callback to dispatcher:

requireActivity().onBackPressedDispatcher.addCallback(this)

I expected that pressing navigate up in toolbar will call it, but it doesn't. Pressing device's back button calls it as expected.

Is there a similar way to add some callback in fragment on navigate up action?

UPDATE

overridden methods onOptionsItemSelected and onSupportNavigateUp doesn't invoked on pressing up button in toolbar

Roko Spark
  • 565
  • 1
  • 5
  • 10

6 Answers6

25

I found a solution

handleOnBackPressed() method invokes only on device's back button click. I wonder, why neither onOptionsItemSelected() nor onSupportNavigateUp() methods haven't been called on pressing "up button" in toolbar. And the answer is I used

NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)

in activity to setup toolbar with navigation component. And that made toolbar responsive for work with navigation internally, pressing "up button" haven't invoked any of overridden methods in activity or fragments.

Instead should be used

NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)

That will make actionBar responsive for navigation, thus I can use overridden functions onOptionsItemSelected() and onSupportNavigateUp() And best place (in my case) to add custom behavior on "up button" click for certain screen is

onSupportNavigateUp()

of hosted activity, like that

override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.mainNavHostFragment)
        return when(navController.currentDestination?.id) {
            R.id.destinationOfInterest -> {
                // custom behavior here 
                true
            }
            else -> navController.navigateUp()
        }
}

But worth to say, that if you want implement custom behavior directly in fragment, answer of @Enzokie should work like a charm

Roko Spark
  • 565
  • 1
  • 5
  • 10
8

You need to call onBackPressed() from onBackPressedDispatcher property. Assuming your Toolbar is properly setup you can use the code below in your Activity.

override fun onOptionsItemSelected(menuItem : MenuItem?) : Boolean {
    if (menuItem.getItemId() == android.R.id.home) {
        onBackPressedDispatcher.onBackPressed()
        return true // must return true to consume it here

    }
    return super.onOptionsItemSelected(menuItem)
}

on Fragment override

override fun onAttach(context: Context) {
        super.onAttach(context)            

        //enable menu
        setHasOptionsMenu(true)

        requireActivity()
                .onBackPressedDispatcher
                .addCallback(this){
                   //true means that the callback is enabled
                    this.isEnabled = true
                    exitDialog() //dialog to conform exit
                }
    }

What this does is :

Trigger a call to the currently added OnBackPressedCallback callbacks in reverse order in which they were added. Only if the most false from its OnBackPressedCallback#handleOnBackPressed() will any previously added callback be called.

I am using AndroidX in my example therefore my import will look like

import androidx.appcompat.app.AppCompatActivity.

R Dewan
  • 15
  • 4
Enzokie
  • 7,365
  • 6
  • 33
  • 39
  • 2
    Thank you for your reply, but thing is onOptionsItemSelected method doesn't invoke when I press "up button". But it does when I select another menu item in toolbar. Tried both in fragment and in activity – Roko Spark Apr 30 '19 at 00:29
  • Can you temporarily replace `onBackPressedDispatcher.onBackPressed()` with a log message? Let me know if it is called. – Enzokie Apr 30 '19 at 00:56
  • 1
    Sure I logged onOptionsItemSelected and it doesn't call. Seems like Navigation component handling "up navigation" event internally. What else worth to mention, that onSupportNavigateUp method also doesn't invoke in Activity – Roko Spark Apr 30 '19 at 01:08
  • FYI onBackPressedDispatcher doesn't have onBackPressed() method, call handleOnBackPressed() method from OnBackPressedCallback interface – Roko Spark May 01 '19 at 01:41
  • `getOnBackPressedDispatcher()` returns `OnBackPressedDispatcher` (in kotlin it is `onBackPressedDispatcher` property) and it has a public method called `onBackPressed()`. The `handleOnBackPressed()` must not be called explicitly because it defeats the purpose of having an interface. Btw this is from `androix.activity`. – Enzokie May 01 '19 at 01:52
  • @RokoSpark you can view the actual framework source [here](https://android.googlesource.com/platform/frameworks/support/+/a5a22718f4d920df72666da8fe2325cfee0bedd7/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java#131) for `OnBackPressedDispatcher`. – Enzokie May 01 '19 at 02:35
  • This wasn't working for me, because the `onOptionsItemSelected` wasn't been called. To fix that you need to call `setHasOptionsMenu(true)` in onCreate in your fragment – xarly Feb 25 '20 at 15:04
6

This set up also works and you won't need to override onSupportNavigateUp in your activity:

NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
toolbar.setNavigationOnClickListener {
    if (navController.currentDestination?.id == R.id.destinationOfInterest) {
        // Custom behavior here
    } else {
        NavigationUI.navigateUp(navController, configuration)
    }
}

I prefer to set up the Toolbar since it will handle automatically the up navigation and open/close a DrawerLayout if you have one.

Juan Cruz Soler
  • 8,172
  • 5
  • 41
  • 44
  • 2
    This opens a way to ` toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }` - was really helpful in my case, thanks. – ror May 02 '20 at 18:04
  • @Juan @ror a combination of your answers worked. Thanks. Docs says there should be a call to `onBackPressedDispatcher.hasEnabledCallbacks()` before calling `onBackPressedDispatcher.onBackPressed() ` – Izak Apr 08 '21 at 16:10
0

I've done it using this in the Fragment alone:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val menuProvider = object : MenuProvider {
        override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        }

        override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
            if (menuItem.itemId == android.R.id.home)
            ...
        }
     }

    lifecycle.addObserver(object : DefaultLifecycleObserver {
        override fun onResume(owner: LifecycleOwner) {
            super.onResume(owner) 
            activity!!.addMenuProvider(menuProvider)
        }

        override fun onPause(owner: LifecycleOwner) {
            super.onPause(owner) 
            activity!!.removeMenuProvider(menuProvider)
        }
    })
}

Also requested to have it here: https://issuetracker.google.com/issues/273299805

android developer
  • 114,585
  • 152
  • 739
  • 1,270
-1

Add click event to toolbar back button in this way

@Override
public boolean onOptionsItemSelected(MenuItem item) {
 switch (item.getItemId()) {
case android.R.id.home:
    // Toolbar back button pressed, do something you want    

default:
    return super.onOptionsItemSelected(item);
  }
}

Another way

  Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    // Title and subtitle
    toolbar.setNavigationOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // Toolbar back button pressed, do something you want    
        }
    });
Asad Ali Choudhry
  • 4,985
  • 4
  • 31
  • 36
  • And if it doesn't work in fragment, and you want to pass that event to fragment from activity, in your activity you must have fragment object when you initialized it, so you can write a method in your fragment, and can call when back button from activity. – Asad Ali Choudhry Apr 29 '19 at 14:01
  • Hi thanks for reply, I don't know why but onOptionsItemSelected method doesn't invokes when I click "up button" neither in fragment nor in activity; clicking another options menu invokes it as expected. I also came up with your another way, but this click listener became global for every screen and not just for specific screen (fragment) – Roko Spark Apr 30 '19 at 00:38
-1

I customized (directly in Fragment) the backbress on Toolbar by using the following steps:

1. onCreate [Activity]:

NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)

2. onSupportNavigateUp [Activity]:

override fun onSupportNavigateUp(): Boolean {
        onBackPressedDispatcher.onBackPressed()
        return super.onSupportNavigateUp()
    }

3. Customize or disable backpress [Fragment]:

requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
           isEnabled = false
           // enable or disable the backpress
       }
Yasser AKBBACH
  • 539
  • 5
  • 7
  • Navigate up should NOT do the same as pressing back. This is therefore a bad solution. Source: https://developer.android.com/guide/navigation/navigation-principles#the_up_button_never_exits_your_app – AplusKminus Aug 22 '22 at 11:11