I was trying out Navigation architecture component and is now having difficulties in setting the title. How do I set the title programmatically and also how it works?
To clear my question, let's have an example, where, I've set up a simple app with MainActivity
hosting the navigation host controller, the MainFragment
has a button and on clicking the button it goes to DetailFragment
.
The same code from another question of multiple app bars on stack-overflow.
MainActivity
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Setting up a back button NavController navController = Navigation.findNavController(this, R.id.nav_host); NavigationUI.setupActionBarWithNavController(this, navController); } @Override public boolean onSupportNavigateUp() { return Navigation.findNavController(this, R.id.nav_host).navigateUp(); } }
MainFragment
public class MainFragment extends Fragment { public MainFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_main, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Button buttonOne = view.findViewById(R.id.button_one); buttonOne.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.detailFragment)); } }
DetailFragment
public class DetailFragment extends Fragment { public DetailFragment() { // Required empty public constructor } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_detail, container, false); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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" android:animateLayoutChanges="true" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" android:theme="@style/AppTheme.AppBarOverlay"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </com.google.android.material.appbar.AppBarLayout> <fragment android:id="@+id/nav_host" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top" android:layout_marginTop="?android:attr/actionBarSize" app:defaultNavHost="true" app:layout_anchor="@id/bottom_appbar" app:layout_anchorGravity="top" app:layout_behavior="@string/appbar_scrolling_view_behavior" app:navGraph="@navigation/mobile_navigation" /> <com.google.android.material.bottomappbar.BottomAppBar android:id="@+id/bottom_appbar" android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:layout_gravity="bottom" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_anchor="@id/bottom_appbar" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
navigation.xml
<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.example.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main" > <action android:id="@+id/toAccountFragment" app:destination="@id/detailFragment" /> </fragment> <fragment android:id="@+id/detailFragment" android:name="com.example.DetailFragment" android:label="fragment_account" tools:layout="@layout/fragment_detail" /> </navigation>
So when start my app, the title is "MainActivity". As usual it shows the MainFragment
that contains the button to go to DetailFragment
. In the DialogFragment
I've set the title as:
getActivity().getSupportActionBar().setTitle("Detail");
First Problem: So clicking the button on the MainFragment
to goto DetailFragment
, it does go there and the title changes to "Detail". But on clicking the back button, the title changes to "fragment_main". So I added this line of code to MainFragment
:
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // ... //Showing the title Navigation.findNavController(view) .getCurrentDestination().setLabel("Hello"); }
Now the while returning back from DetailFragment
to MainFragment
the title changes to "Hello". But here comes the second problem, when I close the app and start again, the title changes back to "MainActivity" though it should be showing "Hello" instead, know?
Ok, then adding setTitle("Hello")
in MainFrgment
is not working too. For example, the activity starts and the title is "Hello", go to DetailsFragment
and press the back button again, the title goes back to "fragment_main".
The only solution is to have both setTitle("Hello")
along with Navigation.findNavController(view).getCurrentDestination().setLabel("Hello")
in MainFragment
.
So what is the proper way to show the title for fragments using Navigation Component?
The drawer icon is displayed on all top-level destinations that use a DrawerLayout . To add a navigation drawer, first declare a DrawerLayout as the root view. Inside the DrawerLayout , add a layout for the main UI content and another view that contains the contents of the navigation drawer.
First, we'll make a file with our navigation graph in it. Create a new android resource file in the res directory as shown below. Under the navigation directory, an empty resource file named nav graph. xml will be created.
It's actually because of:
android:label="fragment_main"
Which you have set in the xml.
So what is the proper way to show the title for
Fragment
s using Navigation Component?
setTitle()
works at this point. But, because you set label for those Fragment
s, it might show the label again when recreating the Activity
. The solution will probably be deleting android:label
and then do your things with code:
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("your title");
Or:
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle("your subtitle");
In onCreateView()
.
Found a workaround:
interface TempToolbarTitleListener { fun updateTitle(title: String) } class MainActivity : AppCompatActivity(), TempToolbarTitleListener { ... override fun updateTitle(title: String) { binding.toolbar.title = title } }
Then:
(activity as TempToolbarTitleListener).updateTitle("custom title")
Check this out too:Dynamic ActionBar title from a Fragment using AndroidX Navigation
As others are still participating in answering this question, let me answer my own question as APIs has changed since then.
First, remove android:label
from the fragment/s that you wish to change the title of, from within navigation.xml
(aka Navigation Graph),.
Now you can change the title from with the Fragment
by calling
(requireActivity() as MainActivity).title = "My title"
But the preferred way you should be using is the API NavController.addOnDestinationChangedListener
from within MainActivity
. An Example:
NavController.OnDestinationChangedListener { controller, destination, arguments -> // compare destination id title = when (destination.id) { R.id.someFragment -> "My title" else -> "Default title" } // if (destination == R.id.someFragment) { // title = "My title" // } else { // title = "Default Title" // } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With