22

I have installed the latest canary version of Android Studio, and followed this (https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing) instruction to implement a simple two page navigation. Basically page1 has a button, and when it is clicked, the app shows page2.

It works, but there is one problem... It does not seem to do anything with the action bar automatically. Is it supposed to show up/back arrow and the "Label" attribute on the action bar automatically by the navigation library? Or am I supposed to do all the work manually as before? I want to show the back arrow and "Details" on action(tool) bar when page2 is showing.

On button click at page 1.

override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
    button1.setOnClickListener {
        val nav = NavHostFragment.findNavController(this);
        nav.navigate(R.id.show_page2)
    }
}

Main activity XML. By default it was the default Action Bar, I have replaced it with a ToolBar. There was no difference.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="?attr/actionBarSize"
        android:elevation="4dp"
        android:background="?attr/colorPrimary"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        android:layout_width="match_parent">
    </androidx.appcompat.widget.Toolbar>

    <fragment
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        app:navGraph="@navigation/nav_graph"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Nav graph XML.

<?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/nav_graph"
            app:startDestination="@id/page1">

    <activity
        android:id="@+id/mainActivity2"
        android:name="com.android.navtest.MainActivity"
        android:label="activity_main"
        tools:layout="@layout/activity_main"/>
    <fragment
        android:id="@+id/page1"
        android:name="com.android.navtest.BlankFragment2"
        android:label="Home page"
        tools:layout="@layout/page1">
        <action
            android:id="@+id/show_page2"
            app:destination="@id/page2"
            app:enterAnim="@anim/anim1"
            app:popExitAnim="@anim/anim2"/>
    </fragment>
    <fragment
        android:id="@+id/page2"
        android:name="com.android.navtest.BlankFragment"
        android:label="Details"
        tools:layout="@layout/page2"/>
</navigation>
ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135

4 Answers4

29

You can connect your ActionBar to a NavController using NavigationUI.setupActionBarWithNavController(). This is generally done in your Activity, right after you call setSupportActionBar():

supportActionBar = findViewById<Toolbar>(R.id.toolbar)

// Get the NavController for your NavHostFragment
val navController = findNavController(R.id.nav_host_fragment)

// Set up the ActionBar to stay in sync with the NavController
setupActionBarWithNavController(navController)

This approach is covered in the Navigation talk at Google I/O 2018.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • I saw examples that used it, but I thought it was about using the bottom navigation, as you see, my main activity does not have a bottom navigation. The examples on the linked video and your code do not show the full source. It is called somewhere in the activity? But the activity does not have `setupActionBarWithNavController`. I could not find any full source code example for this. – Damn Vegetables Jul 08 '18 at 19:55
  • 1
    There are multiple setup methods, one for the action bar, one for bottom nav, one for side nav, etc. If you only want to use the action bar one, only call that one. The code in the video is the entirety of the activity's `onCreate` method. – ianhanniballake Jul 08 '18 at 20:40
  • Yes it had the entire `onCreate`, but in my case the compiler does not resolve `setupActionBarWithNavController`. So, I thought there must be something more to be done with the activity like inheriting something other than `AppCompatActivity`. – Damn Vegetables Jul 08 '18 at 20:48
  • 1
    Did you include the `navigation-ui-ktx` dependency as per the [adding components page](https://developer.android.com/topic/libraries/architecture/adding-components#navigation)? – ianhanniballake Jul 08 '18 at 20:50
  • 1
    When I created a new project and added nav graph, Android Studio asked me if I want it to add dependencies, so I thought it automatically did it for me. I just checked it and it only had added `navigation-fragment`, not `navigation-ui`. So, I manually added it and now it works. – Damn Vegetables Jul 08 '18 at 20:59
  • How can you keep the hamburger menu on going back to the root? It dissappears with this approach. I am sure I am missing something. – A. Steenbergen Mar 21 '19 at 10:40
  • 1
    @A.Steenbergen - the [navigation drawer documentation](https://developer.android.com/guide/navigation/navigation-ui#add_a_navigation_drawer) details how you'd want to use an `AppBarConfiguration` to handle the hamburger menu. – ianhanniballake Mar 21 '19 at 16:07
  • There's a compilation error: **too many arguments for NavController androidx.navigation.fragment** – IgorGanapolsky Dec 11 '19 at 21:41
  • @ianhanniballake hey can you have a look at [this](https://stackoverflow.com/questions/62182885/android-jetpack-navigation-component-condtional-navigation-issue). I am having up/back button sync problem. – androidGeek Jun 04 '20 at 08:18
  • This will not work if you have a BottomNavigationView or you have Gate/Splash screen before a main page. – JPM Nov 03 '21 at 19:12
  • I don't think this code works anymore. When I try it, I get "Activity does not have a NavController set" in the console. – Chucky Jan 19 '22 at 13:26
15

If you want to have navigation back button hidden in more than one place (default is only for home fragment) you can add ids of fragments to AppBarConfiguration and pass this as second parameter of setupActionBarWithNavController, for example:

val appBarConfiguration = AppBarConfiguration(setOf(R.id.splashFragment, R.id.onboardingFragment, R.id.homeFragment))

setupActionBarWithNavController(findNavController(R.id.nav_host), appBarConfiguration)
Rafols
  • 1,299
  • 14
  • 12
3

This is what I have done.
onSupportNavigateUp is called when the user navigates up and it set again.

by calling this setupActionBarWithNavController tell android to update the title of toolbar.

navigateUp Handles the Up button by delegating its behavior to the given NavController. This should generally be called from AppCompatActivity.onSupportNavigateUp().

private lateinit var appBarConfiguration: AppBarConfiguration

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityGameConfigBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_game_config)
        supportActionBar?.show()

        val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
        NavigationUI.setupActionBarWithNavController(this, navController, null)
        appBarConfiguration = AppBarConfiguration.Builder(navController.graph)
            .build()

        NavigationUI.setupWithNavController(binding.navView, navController)
    }
    override fun onSupportNavigateUp(): Boolean {
        val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
        return NavigationUI.navigateUp(navController, appBarConfiguration)
    }
nima moradi
  • 2,300
  • 21
  • 34
2

my solution with binding - the code is in MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
    navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    navController = navHostFragment.navController

    setSupportActionBar(toolbar)//needs to be after binding
    toolbar.setupWithNavController(navController,AppBarConfiguration(navController.graph))
}

as for the titles - first I removed labels (android:label) from the fragments in navigation graph (label overwrites title from what I've tested)

    <fragment
        android:id="@+id/productListFragment"
        android:name="com.example.ProductListFragment"
        android:label="TO_BE_REMOVED"
        tools:layout="@layout/product_list_fragment">
        <action
            android:id="@+id/action_productListFragment_to_mainMenuFragment"
            app:destination="@id/mainMenuFragment" />
    </fragment>

each fragment sets the title and subtitle in onResume, here example from ProductListFragment

override fun onResume() {
    super.onResume()
    val actionBar = (activity as AppCompatActivity).supportActionBar
    actionBar?.title = getString(R.string.product_list_title)
    actionBar?.subtitle = getString(R.string.product_list_subtitle)
}
Stachu
  • 1,614
  • 1
  • 5
  • 17