88

I am using the new Navigation component from Android Jetpack.

The root Activity setup is quite simple:

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

    val navController = findNavController(R.id.navigationFragment)
    setupActionBarWithNavController(navController)

    bottomNavigationView.setupWithNavController(navController)
}

It works well when the Fragment's title is defined in the navigation graph. But for one Fragment, I want to set the title dynamically.

I tried with findNavController().currentDestination.label = "Hello world" but it does nothing.

I could of course use a trick like (activity as? AppCompatActivity)?.supportActionBar?.title = "Hello world", but I feel that it will break the magic that setupActionBarWithNavController() does for me. It there any way to update the Action Bar title dynamically?

JJD
  • 50,076
  • 60
  • 203
  • 339
Jonas Schmid
  • 5,360
  • 6
  • 38
  • 60

16 Answers16

161

As of 1.0.0-alpha08, you can have the NavigationUI bits dynamically set the title... if the dynamic bits are arguments on the navigation action.

So, for example, in your navigation graph, you could have something like this:

  <fragment
    android:id="@+id/displayFragment"
    android:name="com.commonsware.jetpack.sampler.nav.DisplayFragment"
    android:label="Title: {title}" >
    <argument
      android:name="modelId"
      app:argType="string" />
    <argument
      android:name="title"
      app:argType="string" />
  </fragment>

Here, the android:label attribute for our <fragment> has an argument name wrapped in braces ({title} in "Title: {title}". The app bar's title will then be set to the value of the label, with {title} replaced by the value of the title argument.

If you need something more elaborate than that — for example, you want to look up the model by ID and read a property from it — you will need to use more manual approaches, such as those outlined in other answers to this question.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 15
    This solution is not working in navigation version `2.1.0-alpha02`. It directly printing the `Title: {title}` as action bar title. – Rohit Maurya Apr 24 '19 at 16:01
  • @RohitMaurya: If there is no bug report for this, and you can create a project that reproduces the problem, file a bug report. – CommonsWare Apr 24 '19 at 16:06
  • 4
    To add; value binding seems to be sensitive to additional whitespaces in an argument wrapped with braces. I noticed that `{ title }` would cause my app to crash where as `{title}` worked as expected. Just a minor gotcha incase it catches anyone else out. – Dacre Denny Jan 03 '20 at 00:59
  • 1
    @DacreDenny: Nice find! I reproduced that one easily enough and [filed an issue](https://issuetracker.google.com/issues/147083785). – CommonsWare Jan 03 '20 at 01:13
  • Of cause in real live that title would always be a string resource. So we should show the usage of a string resource with an argument instead of a fixed title string that is not translatable. – Marcus Wolschon Feb 21 '20 at 07:19
  • What about subtitle? And what about an object been passed as argument, how to get its String value? – Aliton Oliveira Apr 22 '20 at 16:19
  • @AlitonOliveira: "What about subtitle?" -- AFAIK, `Fragment` does not have that concept. If you are displaying a subtitle in your own code, you would need to handle that yourself. "And what about an object been passed as argument, how to get its String value? " -- for `android:label`, that would appear to be covered in the answer (`title` is an argument). Otherwise, you use the normal Navigation APIs for that, which depend on whether or not you are using the "safe args" plugin. – CommonsWare Apr 22 '20 at 16:37
  • 1
    What if I want to combine a string argument with a string resource? Is that possible? – Benjamin Menrad May 15 '20 at 05:33
  • @BenjaminMenrad: AFAIK, you would have to do that yourself in Java/Kotlin. – CommonsWare May 15 '20 at 10:57
  • @CommonsWare Thanks, I already expected that as I haven't found a way to do that. Nevertheless I wanted to ask :) – Benjamin Menrad May 15 '20 at 12:15
  • So you also have to pass the title as an argument? How is that done? – behelit Jun 29 '20 at 05:58
  • @behelit: You put the `` element in the nav graph, as I have shown above. Any actions that point to this destination would need to provide that argument. Then, your Java/Kotlin code that navigates using that action would need to provide that argument (e.g., via a Safe Args-generated "directions" class). – CommonsWare Jun 29 '20 at 10:48
  • If you want L10N, `android:label="@string/my_title"` and in strings.xml, define `Search: {keyword}` – Tink Aug 28 '20 at 09:02
  • I needed exactly the "more elaborate" option described in this answer: use the `modelId` to asynchronously load the model and then use the model's name as the action bar title. Setting the action bar's title in the fragment's `onResume` method worked. When I rotate the phone, the title is set again to what I want it to be. – Ted Henry Dec 07 '20 at 04:26
  • This solution is great if the name used for the title is known at the time of the fragment creation. However, if the app bar title needs to be changed programmatically after the fragment is created, *[@Patriotic's solution](https://stackoverflow.com/a/60597473/2253682)* is the best fit. – AdamHurwitz Dec 14 '20 at 18:11
  • @RohitMaurya It's working with ver.2.3.5 – Abhijith Brumal Nov 15 '21 at 15:15
  • I don't know why my app sometimes shows `Title: {myTitle}` as fragment title and sometimes it shows `Title: correct Title!`. For this I tried to add `defaultvalue="No Data" but this make app uncompilable, The error is that: selected action accept no argument. – C.F.G Apr 03 '23 at 11:58
43

The title can be changed in the fragment by casting the activity as AppCompatActivity.

Kotlin

(requireActivity() as AppCompatActivity).supportActionBar?.title = "Hello"

Java

((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle("Hello");
AdamHurwitz
  • 9,758
  • 10
  • 72
  • 134
Patriotic
  • 2,103
  • 4
  • 26
  • 36
  • This is the only solution thus far that works with programmatically changing the Navigation UI's action bar/app bar after the `label` is defined in the Navigation Graph. – AdamHurwitz Dec 13 '20 at 02:38
  • Simple and effective. Thanks :D – Mário Henrique May 02 '21 at 02:56
  • Just noticed that Android Studio returns back a warning saying : "Method invocation 'getSupportActionBar' may produce 'NullPointerException'". I was wondering is there any way to resolve this other than putting (if/else) ? – Ehsan Ghasaei Jun 27 '21 at 16:47
  • Doesn't work because fragment references FragmentActivity not AppCompatActivity – JPM Nov 01 '21 at 22:36
  • @JPM it doesn't matter. Fragment references the parent activity. So this works as long as your parent activity is AppCompatActivity. – Sourav Kannantha B Apr 26 '22 at 14:43
  • One issue is if you put this in `onViewCreated` it works on navigation but fails if the fragment is rotated because the action bar is not available in `onViewCreated` when this is called because of the rotation. Instead put it in `onStart`. – Mike Hanafey Jun 26 '22 at 15:13
19

Taking consideration that your host activity is MainActivity, just add the following code to your MainActivity's onCreate fun

val navController = Navigation.findNavController(this, R.id.nav_host_fragment)

// setting title according to fragment
navController.addOnDestinationChangedListener { 
    controller, destination, arguments ->
        toolbar.title = navController.currentDestination?.label
}
JJD
  • 50,076
  • 60
  • 203
  • 339
  • 1
    Agree with this answer if you only have one activity. Simply set label in nav_main.xml – Yuan Fu Jan 28 '19 at 00:09
  • The title must come from the fragment and the code should be in the fragment itself. The activity should not have to know about every possible fragment. That's the reason we move from the activity implementing interfaces to support their fragments to Jetpack navigation and ViewModels. – Marcus Wolschon Feb 21 '20 at 07:22
  • The navigation destination is already given as an argument, so use it: `toolbar.title = destination.label` or `supportActionBar?.title = destination.label` – Pnemonic Jun 04 '21 at 07:54
11

If you use toolbar on setSupportActionBar in Activity and you would like to change its title in fragment, then below code may gonna help you ;)

(requireActivity() as MainActivity).toolbar.title = "Title here"
Ercan
  • 2,601
  • 22
  • 23
9

As of now, The Jetpack Navigation Architecture components do not provide any "built in" way to do this, and you'll have to implement your own "custom" method for doing it.

There is an existing feature request to get functionality for dynamic labels on destinations added to the new Jetpack Navigation Architecture Components. If you are here because you want/need this functionality, please star the existing feature request, here: https://issuetracker.google.com/issues/80267266

Bradleycorn
  • 980
  • 6
  • 9
8

Remove label from graph.xml file

android:label="fragment_info"

and use old school approach if you want to set title of the fragment dynamically from the fragment itself

getActivity().setTitle("Your Title");
Naveen Rao
  • 712
  • 6
  • 10
  • This does not work if you need to make decisions with a navigationListener based on the navigation id, which happens to be the label defined in the nav graph. – Rowan Gontier Jun 08 '20 at 22:20
4

Another solution is to use ViewModel and LiveData, attach viewmodel to your activity and fragments, add a livedata field inside viewmodel

val title = MutableLiveData<String>()

From your activity observe this field, and if it is changed update the toolbar title

viewModel?.title?.observe(this, Observer { 
        my_toolbar.title=it
    })

From your desired fragment change the title field inside the viewmodel

viewModel?.title?.value="New title"
Alex
  • 9,102
  • 3
  • 31
  • 35
  • That could me a solution. However with the navigation component I have way more than one Fragment in the Activity. How would I now in the activity which Fragment is visible and how to react to it. It seems cumbersome. – Jonas Schmid Jun 07 '18 at 11:16
  • You can use multiple fragments in activity, you changing the title from the fragment, it doesn't matter which fragment it is, just change the viewmodel field title to something else, and activity will know about the change in title value, and it will update the toolbar title accordingly – Alex Jun 07 '18 at 12:26
  • With the Navigation component that itself changes the ActionBar title, it does not really seem a good idea. – Jonas Schmid Jun 07 '18 at 13:15
  • 2
    I like this solution, since I can't determine the fragment label when passing the arguments I decided to create base fragment class that changes toolbar title through live data in model and all the subclasses just implement abstract property to provide the title. And I removed all the labels in nav graph, so the Navigation component doesn't interfere. It's not super elegant but I guess it's not so ugly either. – Almighty Dec 21 '18 at 10:23
4

Well, now the Navigation UI supports this feature. Now the ActionBar title changes dynamically. You just have to setup the ActionBar with the NavController.

private lateinit var appBarConfiguration: AppBarConfiguration

private lateinit var navController: NavController

override fun onCreate(savedInstanceState: Bundle?) {
    preferedTheme()
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)
    navController = findNavController(R.id.nav_controller_fragment)
    appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)
}

And set action bar label in nav graph:

<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/mobile_navigation"
        app:startDestination="@id/mainFragment">

<fragment android:id="@+id/mainFragment"
          android:name="com.cinderellaman.general.ui.fragments.MainFragment"
          android:label="General"
          tools:layout="@layout/main_fragment"/>

And now its also support Navigate Up:

override fun onSupportNavigateUp(): Boolean {
    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
JJD
  • 50,076
  • 60
  • 203
  • 339
Hussnain Haidar
  • 2,200
  • 19
  • 30
  • You might want to pimp your code a bit by using extension functions which are available in the `navigation-ui-ktx` artifact, e.g. `BottomNavigationView#setupWithNavController`, `AppCompatActivity#setupActionBarWithNavController` and `NavController#navigateUp`. – JJD Jul 25 '19 at 09:45
2

Until the issue will be fixed, simple listener is working to me:

/**
 * Temporary solution to dynamically change title of actionbar controlled by Navigation component
 * Should be removed as soon as the bug on Navigation will be fixed: (https://issuetracker.google.com/issues/80267266)
 */
interface TempToolbarTitleListener {
    fun updateTitle(title: String)
}

class MainActivity : AppCompatActivity(), TempToolbarTitleListener {

    ...

    override fun updateTitle(title: String) {
        binding.toolbar.title = title
    }
}

change title from fragment:

(activity as TempToolbarTitleListener).updateTitle("custom title")
Francis
  • 6,788
  • 5
  • 47
  • 64
2

Based on @kaustubh-trivedi answer, and if you are using MVVM (like the Android Studio FirstFragment/SecondFragment example):

KOTLIN version

val navController = Navigation.findNavController(this, R.id.nav_host_fragment)

// setting title according to fragment
navController.addOnDestinationChangedListener { 
    controller, destination, arguments ->
        toolbar.title = navController.currentDestination?.label
}

JAVA version

        navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
            @Override
            public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
                binding.toolbar.setTitle(destination.getLabel());
            }
        });
danielrosero
  • 596
  • 8
  • 14
1

You can add addOnNavigatedListener inside your activity, and based on current destination change the title

 findNavController(nav_host_fragment).addOnNavigatedListener { controller, destination ->
        when(destination.id) {
            R.id.destination1 -> {
                my_toolbar.title= "Some title"
            }
            R.id.destination2 -> {
                my_toolbar.title= "Othertitle"

            }

    }
}
Alex
  • 9,102
  • 3
  • 31
  • 35
1

If your title is received from a concatenated string, for example:

ActionBar actionBar = ((AppCompatActivity) 
  requireActivity()).getSupportActionBar();
  
if(actionBar!=null)                   
   actionBar.setTitle(count + "items"); 
Ayia
  • 146
  • 6
1

As per the below solutions, there is no need to access the toolbar manually.

If you are going to pass the Model into the destinations fragment. Then you can do as below.

Override toString() method in your model class

data class UserModel(
    val userId: String = ""
    val firstName: String = "",
    val lastName: String = ""  
) {

    override fun toString(): String {
        return "$firstName $lastName"
    }
}

Now, in the nav_grap.xml file, do as below. android:label="{UserData}" will be fetch label from toString() method of model class.

<fragment
    android:id="@+id/messagesFragment"
    android:name="com.app.mydemoapp.ui.messages.MessagesFragment"
    android:label="{UserData}"
    tools:layout="@layout/fragment_messages">

    <argument
        android:name="UserData"
        app:argType="com.app.mydemoapp.model.UserModel" />

</fragment>

I hope, this will be helpful.

Parth Patel
  • 859
  • 2
  • 11
  • 28
0

On trying the activity's title it seems to override the title for fragment. Being on safe side you must put on onResume.

override fun onResume() {
    super.onResume()
    activity?.toolbar.title = "YOUR_TITLE_HERE"
}

it works for me !

Note : Must have Toolbar Widget in activity

Add toolbar like this in your activity's xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.appbar.AppBarLayout>

    <!-- Other Widgets -->

</androidx.coordinatorlayout.widget.CoordinatorLayout>
Dilroop Singh
  • 544
  • 6
  • 16
0

Just use this code inside yourfragment.java if you are using navigation components , and you want to change the label of your fragment programmatically in JAVA

  Navigation.findNavController(view).addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
        @Override
        public void onDestinationChanged(@NonNull NavController navController, @NonNull NavDestination navDestination, @Nullable Bundle bundle) {

           navController.findDestination(R.id.YOUR_FRAGMENT_ID_HERE).setLabel(YOUR_LABEL_HERE);

        }
    });
Sabir Ali
  • 1
  • 1
  • does this overrides what we have defined using `addOnDestinationChangedListener` on the main activity? – C.F.G Jul 20 '23 at 03:17
-2

you can remove android:label in navigation graph then write in onCreateView()

activity?.title="your title"
Amina Bekir
  • 220
  • 2
  • 9
  • In case someone wonders why the negative votes: this solution is far from valid because the question already specifies that the activity is using a toolbar with `setupActionBarWithNavController()`, therefore, using `activity?.title` won't ever work because it's being overriden by the `toolbar`'s title. – Juan José Melero Gómez Jan 06 '23 at 01:50