Navigation Component, how to show a normal UP/back button instead of hamburger icon in Navigation Drawer
H

3

1

I have three top-level destination fragments in my activity:

appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.trackerFragment,
            R.id.nutrientFragment,
            R.id.settingsFragment
        ),
        drawerLayout
    )

Which means all these fragments will show the hamburger button instead of the normal back arrow. The problem is that I have a "Go to Nutrient" button in my App that navigates to NutrientFragment, which is a top level destination. I would like that this fragment still showed the hamburger icon when navigating with the drawer, but a normal back/up button when navigating through the "Go to Nutrient" button.

My initial idea was to pass a boolean value as an argument when navigating to NutrientFragment, and inside onCreateView(), depending on the value of the boolean, remove the hamburger icon or add the back button. But that seems a little ugly and inefficient, specially if in the future I add more destinations that could have the same problem. I also have no idea how to "remove the hamburger icon and replace with the normal back button" either. Any idea on how to implement that? All the answers that I found are either in old deprecated java code or not quite what I am looking for.

Thanks.

Henandchickens answered 17/6, 2021 at 21:27 Comment(2)
Navigation components allow to show or hide the UP button based on appBarConfiguration; if you have a custom design, you need to revert back to customize it with android API, and in your case like ((requireActivity() as AppCompatActivity).supportActionBar)?.setDisplayHomeAsUpEnabled(boolean)Imbibe
Unfortunately that doesn't work. The hamburger icon still shows.Henandchickens
H
0

I managed to find a solution. I added this variable to my MainActivity:

// If true, disable drawer and enable back/up button
private var isUpButton = false

And these methods:

// Disable drawer and enable the up button
fun useUpButton() {
    supportActionBar?.setHomeAsUpIndicator(
        androidx.appcompat.R.drawable.abc_ic_ab_back_material
    )
    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
    isUpButton = true
}

// Enable the drawer and disable up button
private fun useHamburgerButton() {
    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
    isUpButton = false
}

useUpButton() replaces the hamburger icon with the default up icon, locks the drawer slider and sets isUpButton to true. I also overrode onOptionsItemsSelected:

// If isUpButton is true, and the home button is clicked, navigate up and
// enable drawer again, if false, just normal drawer behaviour
override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return if (isUpButton && item.itemId == android.R.id.home) {
        navController.navigateUp()
        useHamburgerButton()
        true
    } else super.onOptionsItemSelected(item)
}

So if the home button (either the hamburger icon or up icon) is clicked, and isUpButton is set to true, the activity will navigate up, just like normal up behaviour, and reset the isUpButton to false, so other fragments can enjoy the drawer again.

Now all I need to do is use these methods to solve my problem: I want the NutrientFragment to show the "up button" ONLY when I navigate to it WITHOUT using the drawer. To do that I need to create an argument to the NutrientFragment destination on my nav_graph.xml:

<fragment
    android:id="@+id/nutrientFragment"
    android:name="com.example.elegantcalorietracker.ui.fragments.NutrientFragment"
    android:label="@string/nutrients"
    tools:layout="@layout/fragment_nutrient">
    <action
        android:id="@+id/action_nutrientFragment_to_trackerFragment"
        app:destination="@id/trackerFragment" />
    <argument
        android:name="upButtonNeeded"
        android:defaultValue="false"
        app:argType="boolean" />

And add this to the onCreateView() method on NutrientFragment:

upButtonNeeded = arguments?.getBoolean("upButtonNeeded") ?: false
if (upButtonNeeded) {
    (activity as MainActivity).useUpButton()
}

Now, if I have a button in another fragment that navigates to NutrientFragment, all I need to do is pass true as an argument to the navigate method:

val argument = bundleOf("upButtonNeeded" to true)
this@TrackerFragment
     .findNavController()
     .navigate(
         R.id.action_trackerFragment_to_nutrientFragment,
         argument
     )

Result:

When navigating to NutrientFragment using the drawer, the hamburger icon shows normally, but when navigating to it another way, like a button, the up button shows instead:

Solution gif

Henandchickens answered 19/6, 2021 at 1:51 Comment(2)
this is so wrong and bad code. You have no idea how to use Navigation Component with Toolbar and Navigation Drawer. Navigation Component automatically handles this logic for you. But you decided to change icon by yourself....Melville
@Melville Saying the code is bad while not giving any better solution doesn't help anyone. Would you mind posting a better answer? I had to come up with something since I didn't find any answer. I would appreciate if you could help me make it better.Henandchickens
I
6

Given:

Now you have a navigation drawer with three fragments (trackerFragment, nutrientFragment, & settingsFragment).

appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.trackerFragment,
            R.id.nutrientFragment,
            R.id.settingsFragment
        ),
        drawerLayout
    )

Required:

You want to keep the burger in the home fragment (NutrientFragment) and replace it with the UP button for some fragment.

Solution:

Assuming that settingsFragment is the fragment is that you need to have the UP button instead of the burger.

First remove this fragment from the appBarConfiguration:

appBarConfiguration = AppBarConfiguration(
    setOf(
        R.id.trackerFragment, R.id.nutrientFragment
    ), drawerLayout
)

Then, in order to control the Burger button, you need to sync the drawer layout and the SupportActionBar with an ActionBarDrawerToggle and here is a method for that:

fun getActionBarDrawerToggle(
    drawerLayout: DrawerLayout,
    toolbar: Toolbar
): ActionBarDrawerToggle {
    val toggle = ActionBarDrawerToggle(
        this,
        drawerLayout,
        toolbar,
        R.string.open,
        R.string.close
    )
    drawerLayout.addDrawerListener(toggle)
    toggle.syncState()
    return toggle
}

R.string.open & R.string.close are notation strings, you can create them in strings.xml:

<resources>
    <string name="open">Open</string>
    <string name="close">Close</string>
</resources>

And the toolBar is the Toolbar which is inflated from the layout and set as the supportActionBar with setSupportActionBar()

Then, add a listener to the navigation to detect when the settingFragment is selected, so that you can disable the toggle functionality of the burger using toggle.isDrawerIndicatorEnabled:

val toggle = getActionBarDrawerToggle(drawerLayout, toolbar) // replace  the `drawerLayout` & `toolbar` according to yours
navController.addOnDestinationChangedListener { _, destination, _ ->
    if (destination.id == R.id.settingsFragment)
        toggle.isDrawerIndicatorEnabled = false
}

Of course, now the UP button won't work whenever you click on it, to fix this we need to setToolbarNavigationClickListener in order to enable the toggle functionality and navigate back to the Home fragment (assuming the home fragment is nutrientFragment):

toggle.setToolbarNavigationClickListener {
    toggle.isDrawerIndicatorEnabled = true
    navController.navigate(R.id.nutrientFragment)
}

Preview:

Here's the Gallery fragment is where is the UP button is placed:

Imbibe answered 18/6, 2021 at 15:49 Comment(3)
This is not quite what I was looking for. I still want all fragments to show the hamburger icon, but if I were to navigate to any of these fragments in any other way that isn't the drawer, like another button inside the app, I want the up button to be showed instead. But your answer helped me come up with an idea that was the solution. Check my answer to the post, and if possible, help me better it, it is a solution but is a little ugly and the ui is a little laggyHenandchickens
@hahmraro It's probably quite different, but it will give you the general idea of controlling the burger/UP button .. The customization is totally yours.Imbibe
@Imbibe how can I override Menu and Back icons? #69812862Melville
H
0

I managed to find a solution. I added this variable to my MainActivity:

// If true, disable drawer and enable back/up button
private var isUpButton = false

And these methods:

// Disable drawer and enable the up button
fun useUpButton() {
    supportActionBar?.setHomeAsUpIndicator(
        androidx.appcompat.R.drawable.abc_ic_ab_back_material
    )
    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
    isUpButton = true
}

// Enable the drawer and disable up button
private fun useHamburgerButton() {
    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
    isUpButton = false
}

useUpButton() replaces the hamburger icon with the default up icon, locks the drawer slider and sets isUpButton to true. I also overrode onOptionsItemsSelected:

// If isUpButton is true, and the home button is clicked, navigate up and
// enable drawer again, if false, just normal drawer behaviour
override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return if (isUpButton && item.itemId == android.R.id.home) {
        navController.navigateUp()
        useHamburgerButton()
        true
    } else super.onOptionsItemSelected(item)
}

So if the home button (either the hamburger icon or up icon) is clicked, and isUpButton is set to true, the activity will navigate up, just like normal up behaviour, and reset the isUpButton to false, so other fragments can enjoy the drawer again.

Now all I need to do is use these methods to solve my problem: I want the NutrientFragment to show the "up button" ONLY when I navigate to it WITHOUT using the drawer. To do that I need to create an argument to the NutrientFragment destination on my nav_graph.xml:

<fragment
    android:id="@+id/nutrientFragment"
    android:name="com.example.elegantcalorietracker.ui.fragments.NutrientFragment"
    android:label="@string/nutrients"
    tools:layout="@layout/fragment_nutrient">
    <action
        android:id="@+id/action_nutrientFragment_to_trackerFragment"
        app:destination="@id/trackerFragment" />
    <argument
        android:name="upButtonNeeded"
        android:defaultValue="false"
        app:argType="boolean" />

And add this to the onCreateView() method on NutrientFragment:

upButtonNeeded = arguments?.getBoolean("upButtonNeeded") ?: false
if (upButtonNeeded) {
    (activity as MainActivity).useUpButton()
}

Now, if I have a button in another fragment that navigates to NutrientFragment, all I need to do is pass true as an argument to the navigate method:

val argument = bundleOf("upButtonNeeded" to true)
this@TrackerFragment
     .findNavController()
     .navigate(
         R.id.action_trackerFragment_to_nutrientFragment,
         argument
     )

Result:

When navigating to NutrientFragment using the drawer, the hamburger icon shows normally, but when navigating to it another way, like a button, the up button shows instead:

Solution gif

Henandchickens answered 19/6, 2021 at 1:51 Comment(2)
this is so wrong and bad code. You have no idea how to use Navigation Component with Toolbar and Navigation Drawer. Navigation Component automatically handles this logic for you. But you decided to change icon by yourself....Melville
@Melville Saying the code is bad while not giving any better solution doesn't help anyone. Would you mind posting a better answer? I had to come up with something since I didn't find any answer. I would appreciate if you could help me make it better.Henandchickens
C
0

Just removing from appBarConfiguration will do

Connell answered 20/10, 2023 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.