Search code examples
androidkotlinandroid-fragmentsandroid-toolbarandroid-architecture-navigation

Custom Toolbar for each fragment using Navigation component


I use Navigation component with a single activity. The idea to create custom toolbar for each Fragment. For example one toolbar has to be yellow, another purple with menu icons, another transparent. And also if it possible I would like to implement it with saving connection between navigation and toolbar by setupActionBarWithNavController(navController) in MainActivity. I tried to use this method (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) in Fragment but it duplicated toolbar.

This is my theme

<style name="Theme.Movies" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>

This is toolbar in MainActivity layout

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

This is MainActivity

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        setSupportActionBar(toolbar)

        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

        navController = navHostFragment.navController

        setupActionBarWithNavController(navController)
        bottom_nav.setupWithNavController(navController)

    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }
}

Solution

  • I tried to use this method (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) in Fragment but it duplicated toolbar.

    This is expected because the toolbar is hosted by the activity, and as Jetpack navigation architecture components uses a single-activity model, then all navGraph fragments hosted by this activity share this toolbar (because it's a part of the activity).

    And therefore resetting the toolbar again will duplicate it.

    The solution to this is to remove the toolbar from the activity, and use a unique toolbar within every fragment layout, this way when a fragment is transacted to another; the layout of the old fragment is replaced with the new layout of the new fragment, so the old toolbar is gone, and we've a new toolbar; and at this time you need to call setSupportActionBar(toolbar) for the new toolbar.

    But notice that every time you call setSupportActionBar(toolbar) you have to recall setupActionBarWithNavController() because it needs to be attached to the new toolbar.

    UPDATE

    So, I have to call setSupportActionBar(toolbar) and setupActionBarWithNavController() in Fragment?

    Actually you can call setSupportActionBar(toolbar) in the activity. But you can use requireActivity() in fragment to do that:

    In order not to repeat things, you can have a function the activity to do that:

    In activity:

    fun setupActionBar(toolBar: Toolbar) {
    
        setSupportActionBar(toolbar)
    
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    
        navController = navHostFragment.navController
    
        setupActionBarWithNavController(navController)
        
    }   
    

    And in fragment:

    val toolbar = findViewById<Toolbar>(R.id.foo)
    (requireActivity() as MainActivity).setupActionBar(toolbar)