I'm trying out androidx navigation component and have setup my activity with a toolbar a container. I'm doing this in one of the intermittent screens of my app which has a lot of internal navigation/steps and I thought I could navigation arch component a try with this.
Since this is an intermittent screen, I want to display a back button on the toolbar from the first screen itself.
I've already setup the toolbar with the below code in the onCreate() method of my host activity,
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
NavigationUI.setupActionBarWithNavController(this, navHostFragment.getNavController());
I can see the back button/back arrow on the second screen of my graph but not on the first screen.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation"
app:startDestination="@id/listing">
<fragment
android:id="@+id/listing"
android:name=".ui.ItemsListingFragment"
android:label="Items"
tools:layout="@layout/items_listing" >
<action
android:id="@+id/listToQuantity"
app:destination="@id/quantity"
/>
<action
android:id="@+id/listToReason"
app:destination="@id/reason"
/>
</fragment>
<fragment
android:id="@+id/quantity"
android:name=".ui.ItemQuanitySelectionFragment"
android:label="Items"
tools:layout="@layout/fragment_item_quanity_selection" >
<action
android:id="@+id/quantityToReason"
app:destination="@id/reason"
/>
</fragment>
<fragment
android:id="@+id/reason"
android:name=".ui.ItemReasonFragment"
android:label="Items"
tools:layout="@layout/fragment_item_reason">
</fragment>
</navigation>
What more changes do I need to make to add the back button on the toolbar right from the first step.
As you noted, navigation component show back button on all destinations except top-level. It uses an AppBarConfiguration
to determine which destinations are considered 'top-level'.
So you need to:
1) create AppBarConfiguration without top-level destinations
2) call a 3-argument version of setupActionBarWithNavController
AppBarConfiguration abc = new AppBarConfiguration.Builder().build();
NavigationUI.setupActionBarWithNavController(this, navHostFragment.getNavController(), abc)
To customise up button behavior on 'top-level' destination you can set fallback OnNavigateUpListener
in your appbar configuration builder:
new AppBarConfiguration.Builder().setFallbackOnNavigateUpListener(listener).build()
More info from sources:
click on up button calles NavigationUI.navigateUp(NavController, AppBarConfiguration)
which in turn calles NavController.navigateUp()
which tries to pop the backstack. As it cannot pop back stack, it just does nothing and returns false. Your fallback listener will be called in that case.
When calling setUpActionBarWithNavCntroller
you have to override the function onSupportNavigateUp
, like this:
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.navHostFragment).navigateUp()
|| super.onSupportNavigateUp()
}
1- I created an interface to show/hide up button from the nav host activity.
interface ShowUpButtonListener {
fun showUpButton()
fun hideUpButton()
}
2- 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)
}
3- Here the method in the activity when up button pressed. If in start destination, implement onBackPressed():
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host)
if(!navController.navigateUp()){ // When in start destination
onBackPressed()
}
return navController.navigateUp()
}
4- In a fragment create show up button listener property:
private lateinit var showUpButtonListener: ShowUpButtonListener
5- Assign property to context activity:
override fun onAttach(context: Context) {
super.onAttach(context)
if(context is ShowUpButtonListener){
showUpButtonListener = context
}
}
6- In the fragment call hide or show up button from the interface property:
fun someFunction() {
showUpButtonListener.showUpButton()
}
7- 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() {
}
})
}