Android
Menu
Dynamic Text
Development
Programming

How to change menu item text dynamically in Android

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Changing a menu item's title at runtime is common when the UI reflects state such as login, edit mode, or unsaved changes. The key is to update the menu during the correct lifecycle phase and trigger a refresh when state changes, rather than setting the title once and assuming Android will update it automatically later.

Understand When Menu Text Is Evaluated

In an Activity, the menu is usually inflated in onCreateOptionsMenu, but dynamic state should usually be applied in onPrepareOptionsMenu. That method can run again after you call invalidateOptionsMenu(), which is what makes runtime updates visible.

If you only change the title during menu creation, later state changes may never show up in the toolbar.

Activity Pattern With invalidateOptionsMenu

This is the classic pattern for an Activity that changes a menu title when auth state changes:

kotlin
1class MainActivity : AppCompatActivity() {
2
3    private var loggedIn = false
4
5    override fun onCreateOptionsMenu(menu: Menu): Boolean {
6        menuInflater.inflate(R.menu.main_menu, menu)
7        updateProfileTitle(menu)
8        return true
9    }
10
11    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
12        updateProfileTitle(menu)
13        return super.onPrepareOptionsMenu(menu)
14    }
15
16    private fun updateProfileTitle(menu: Menu) {
17        val item = menu.findItem(R.id.action_profile)
18        item.setTitle(if (loggedIn) R.string.menu_account else R.string.menu_sign_in)
19    }
20
21    fun onAuthStateChanged(isLoggedIn: Boolean) {
22        loggedIn = isLoggedIn
23        invalidateOptionsMenu()
24    }
25}

The refresh call is the important part. It tells Android to rebuild the visible menu state using the latest data.

Modern Fragment Pattern With MenuProvider

For fragments, the modern pattern is to use a MenuProvider tied to the fragment view lifecycle.

kotlin
1class HomeFragment : Fragment() {
2
3    private var hasDraft = false
4
5    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6        val menuHost: MenuHost = requireActivity()
7
8        menuHost.addMenuProvider(object : MenuProvider {
9            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
10                menuInflater.inflate(R.menu.home_menu, menu)
11            }
12
13            override fun onPrepareMenu(menu: Menu) {
14                menu.findItem(R.id.action_save).setTitle(
15                    if (hasDraft) R.string.menu_update else R.string.menu_save
16                )
17            }
18
19            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
20                return false
21            }
22        }, viewLifecycleOwner, Lifecycle.State.RESUMED)
23    }
24
25    private fun onDraftChanged(value: Boolean) {
26        hasDraft = value
27        requireActivity().invalidateOptionsMenu()
28    }
29}

This keeps menu ownership aligned with the fragment lifecycle and avoids stale references to destroyed views.

Keep Titles in String Resources

Even when the title is dynamic, the actual text should still come from string resources.

kotlin
private fun titleResForState(loggedIn: Boolean): Int {
    return if (loggedIn) R.string.menu_account else R.string.menu_sign_in
}

That matters for localization, testing, and theme consistency. Hardcoded strings tend to survive much longer in codebases than intended and become an internationalization problem later.

Drive Menu State From Real UI State

In larger apps, menu text often depends on ViewModel state rather than on one local field. The update logic should still stay small and deterministic.

kotlin
1viewLifecycleOwner.lifecycleScope.launchWhenStarted {
2    viewModel.uiState.collect { state ->
3        hasDraft = state.hasDraft
4        requireActivity().invalidateOptionsMenu()
5    }
6}

This pattern keeps the menu reactive without scattering title changes across button handlers, callbacks, and lifecycle methods.

Also remember that action items can move into the overflow menu on smaller screens. A dynamic title that is clear in the toolbar should still make sense when shown in overflow.

Common Pitfalls

The most common mistake is updating the underlying state but forgetting to call invalidateOptionsMenu, so the title never refreshes.

Another common issue is setting the title only in onCreateOptionsMenu and expecting later state changes to appear automatically. Developers also often hardcode the text instead of using resources, which creates localization problems, or they mutate menu state from places that are not lifecycle-safe for the current fragment.

Summary

  • Use onPrepareOptionsMenu or MenuProvider.onPrepareMenu for dynamic menu titles.
  • Call invalidateOptionsMenu() whenever the state that drives the title changes.
  • Keep the displayed strings in resources, not inline literals.
  • For fragments, prefer MenuProvider tied to the view lifecycle.
  • Derive menu text from real app state rather than from scattered ad hoc flags.

Course illustration
Course illustration

All Rights Reserved.