84

How do I declare a menu inside of Android fragment? The method that I had used previously is now deprecated.

Originally:

    override fun onCreateView(...): View {
        setHasOptionsMenu(true)
    }
 override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        this.menu = menu
        inflater.inflate(R.menu.menu, this.menu)
    }
SUR4IDE
  • 1,003
  • 1
  • 5
  • 11
  • 9
    Did you read [the release notes](https://developer.android.com/jetpack/androidx/releases/fragment#1.5.0-alpha04) which linked to the [code snippets](https://developer.android.com/jetpack/androidx/releases/activity#1.4.0-alpha01)? – ianhanniballake Apr 18 '22 at 23:01
  • 1
    Why exactly was it deprecated? I don't see anything special about the new code... – android developer May 25 '22 at 01:04
  • 2
    "The Fragment APIs for providing a menu to your activity’s ActionBar have been deprecated as they tightly couple your fragment to your activity and are not testable in isolation. ", from the release notes mentioned above. – Fakrudeen Oct 12 '22 at 20:32

10 Answers10

122

From the Developer documentation, this can be achieved by the following:

/**
  * Using the addMenuProvider() API directly in your Activity
  **/
class ExampleActivity : ComponentActivity(R.layout.activity_example) {

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

    // Add menu items without overriding methods in the Activity
   addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.example_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
        return true
      }
    })
  }
}

/**
  * Using the addMenuProvider() API in a Fragment
  **/
class ExampleFragment : Fragment(R.layout.fragment_example) {

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // The usage of an interface lets you inject your own implementation
    val menuHost: MenuHost = requireActivity()
  
    // Add menu items without using the Fragment Menu APIs
    // Note how we can tie the MenuProvider to the viewLifecycleOwner
    // and an optional Lifecycle.State (here, RESUMED) to indicate when
    // the menu should be visible
    menuHost.addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.example_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
            return when (menuItem.itemId) {
                R.id.menu_clear -> {
                    // clearCompletedTasks()
                    true
                }
                R.id.menu_refresh -> {
                    // loadTasks(true)
                    true
                }
                else -> false
            }
      }
    }, viewLifecycleOwner, Lifecycle.State.RESUMED)
  }

Fragments setHasOptionsMenu deprecated, use setHasOptionsMenu

Joseph Wambura
  • 2,732
  • 2
  • 21
  • 24
  • 1
    What about other functions, such as invalidation and getting a reference to the menu items? Those still exist, or also changed? – android developer May 23 '22 at 06:13
  • in fragment I would need to keep reference to the MenuProvider object so I can remove it by using removeMenuProvider(MenuProvider obj) cause of duplicated menus across multiple fragments ....you known how to do? Just tried it out but still have the issue – riccardogabellone Jun 30 '22 at 14:15
  • 1
    @riccardogabellone I got a similar issue, but I've noticed you can just pass the LifecycleOwner parameter to the addMenuProvider function, meaning in the fragment I use this: `activity!!.addMenuProvider(menuProviderThatYouCreated, this.viewLifecycleOwner)` – android developer Jul 06 '22 at 20:34
  • What does following mean?: // The usage of an interface lets you inject your own implementation – David Jul 06 '22 at 23:53
  • 3
    `val menuHost: MenuHost = requireActivity()` in fragment I see it's Required: MenuHost but Found: FragmentActivity – Dr Mido Jul 22 '22 at 11:44
  • But how can we handle the back arrow press? – Stefan Jul 27 '22 at 15:36
  • This looks weird. you have to inflate the xml twice? In activity and fragment? Or it can be in fragment only? – chitgoks Sep 02 '22 at 04:41
32

Expanding on what @joseph-wambura and @hammad-zafar-bawara said, you can also implement the interface in the fragment...

class MyFragment : Fragment(), MenuProvider {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // Do stuff...
        val menuHost: MenuHost = requireActivity()
        menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menuInflater.inflate(R.menu.options, menu)
        // Do stuff...
    }

    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Do stuff...
        return false
    }
}
rogue
  • 341
  • 3
  • 7
  • 1
    `val menuHost: MenuHost = requireActivity()` in fragment I see it's Required: MenuHost but Found: FragmentActivity How can use it? – Dr Mido Jul 22 '22 at 11:45
  • Make sure that you are using `import androidx.fragment.app.Fragment` and not the depreciated `import android.app.Fragment` – rogue Jul 23 '22 at 18:37
  • I already use this – Dr Mido Jul 23 '22 at 19:38
  • This is a forced ComponentActivity (and by extension its fragments) change. There are devs still using CompatActivity without the forced component framework and the guides do not explain how to accomplish this for that scenario. requireActivity() in that case throws a type mismatch. – Shadow Jul 26 '22 at 19:17
  • 1
    The solution for this is cast it, like so `val menuHost: MenuHost = requireActivity() as MenuHost` – Shadow Jul 26 '22 at 19:51
  • What is viewLifecycleOwner? Can I just use menuHost.addMenuProvider(this);? – len Jul 30 '22 at 13:44
  • You could do that @len, but (from my reading of the [release notes](https://developer.android.com/jetpack/androidx/releases/activity#1.4.0-alpha01)) you would then be responsible for calling `menuHost.removeMenuProvider(this)` when the fragment is destroyed. – rogue Jul 31 '22 at 18:25
  • MenuProvider was added to the class implementation. That is why he can use requireActivity() directly. – Alexander Langer Sep 07 '22 at 17:37
25

In Kotlin, declaration for Activity, Fragment and PreferenceFragmentCompat

Activity

class MainActivity : AppCompatActivity() {

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

   addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menuInflater.inflate(R.menu.main_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
        return true
      }
    })
  }
}

Fragment

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    // The usage of an interface lets you inject your own implementation
    val menuHost: MenuHost = requireActivity()

    menuHost.addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.main_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
            return when (menuItem.itemId) {
                R.id.action_menu1 -> {
                    // todo menu1
                    true
                }
                R.id.action_menu2 -> {
                    // todo menu2
                    true
                }
                else -> false
            }
      }
    }, viewLifecycleOwner, Lifecycle.State.RESUMED)
 }

PreferenceFragmentCompat

val menuHost: MenuHost = requireHost() as MenuHost
//Same declaration with Fragment

Use MenuProvider interface

class FirstFragment : Fragment(), MenuProvider {
 
      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
              val menuHost: MenuHost = requireActivity()
              menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
      }
 
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.second_menu, menu)
      }
 
      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
            return when (menuItem.itemId) {
                R.id.menu_clear -> {
                    // Do stuff...
                    true
                }
                R.id.menu_refresh -> {
                    // Do stuff...
                    true
                }
                else -> false
            }
      }, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
Codelaby
  • 2,604
  • 1
  • 25
  • 25
  • Based from my tweet and personal blog notes, https://twitter.com/Codelaby/status/1558745982803869705?s=20&t=6ap1lh7Du3XjLP2DcEkxSQ – Codelaby Aug 14 '22 at 10:47
  • I tried but `val menuHost: MenuHost = requireActivity()` giving error: `Type mismatch: inferred type is FragmentActivity but MenuHost was expected` – Joshua Sep 13 '22 at 04:40
  • 1
    After adding `implementation 'androidx.activity:activity-compose:1.5.1'` error disappeared thanx – Joshua Sep 13 '22 at 05:22
  • Please can you tell me what are the purpose of these two`` viewLifecycleOwner, Lifecycle.State.RESUMED`` – Ahmed Elsayed Jan 26 '23 at 19:40
  • With this parameter, you can tell the menu at what point in the fragment's life cycle it should be visible (in this case, the resume state). – Codelaby Jan 27 '23 at 14:11
11

JAVA CODE

Option menu in Activity

addMenuProvider(new MenuProvider() {
            @Override
            public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
                menuInflater.inflate(R.menu.bottom_nav_menu, menu);

                // Add menu options here

            }

            @Override
            public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {

                // Handle Menu Options Selection Here

                return false;
            }
        });

Option menu in Fragment

requireActivity().addMenuProvider(new MenuProvider() {
            @Override
            public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
                menuInflater.inflate(R.menu.bottom_nav_menu, menu);
                
                // Add option Menu Here
                
            }

            @Override
            public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
                return false;
                
                // Handle option Menu Here
                
            }
        }, viewLifecycleOwner, Lifecycle.State.RESUMED);
Angel Koh
  • 12,479
  • 7
  • 64
  • 91
8

If you're using Jetpack NavigationUI then you need to setSupportActionBar(toolbar), otherwise the menu will not be present.

Eric B.
  • 673
  • 11
  • 16
4

this helps me on onCreateView Method:

requireActivity().addMenuProvider(new MenuProvider() {
        @Override
        public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
            menuInflater.inflate(R.menu.bottom_nav_menu, menu);
            
            // Add option Menu Here
            
        }

        @Override
        public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
                           
            // Handle option Menu Here
             return false;
        }
    }, getViewLifecycleOwner, Lifecycle.State.RESUMED);
Ali Ahmed
  • 63
  • 5
  • What if the menuprovider is in the main activity and i only want to add some code when a menu item is clicked when the fragment is in view/focused? Do i also have to inflate in onCreateMenu? – chitgoks Sep 02 '22 at 03:02
2

I'm not sure why all answers recommend passing in Lifecycle.State.RESUME as the lifecycle state to the addMenuProvider call. This will make the menu disappear when the fragment is paused, which doesn't look great when the fragment is paused and still visible.

For example, showing a dialog as a result of tapping a menu item will make the menu disappear. It will re-appear when the dialog is dismissed.

For most cases, a better lifecycle state to pass in would be Lifecycle.State.CREATE, which will only remove the menu when the view is destroyed. This is also the default behaviour, so you can simply omit the lifecycle state.

Eran Boudjnah
  • 1,228
  • 20
  • 22
1

For people who have flashbacks from Java, here is a bit better way using this extension function:

fun Fragment.addMenuProvider(@MenuRes menuRes: Int, callback: (id: Int) -> Boolean) {
    val menuProvider = object : MenuProvider {
        override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
            menuInflater.inflate(menuRes, menu)
        }

        override fun onMenuItemSelected(menuItem: MenuItem) = callback(menuItem.itemId)

    }
    (requireActivity() as MenuHost).addMenuProvider(
        menuProvider,
        viewLifecycleOwner,
        Lifecycle.State.RESUMED
    )
}

and the usage:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    addMenuProvider(R.menu.feed_fragment) {
        when (it) {
            R.id.menu_write_post -> viewModel.goToPostCreation()
            R.id.menu_filter -> viewModel.goToFilter()
            else -> false
        }
    }
}

In case there is a need for feature flag, then you can repeat addMenuProvider call multiple times

George Shalvashvili
  • 1,263
  • 13
  • 21
0

in java for fragment i tried this . it works fine for me

 @Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.layout_example, container, false);

    toolbar = (Toolbar)view.findViewById(R.id.toolbar_1);
    ((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
    MenuHost menuHost = requireActivity();
    menuHost.addMenuProvider(new MenuProvider() {
        @Override
        public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
            menuInflater.inflate(R.menu.menu_search,menu);
        }

        @Override
        public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {

            if (menuItem.getItemId() == R.id.search_friend){
                Toast.makeText(getActivity(), "friends", Toast.LENGTH_SHORT).show();
                return true;
            }
            else return false;
        }
    },getViewLifecycleOwner(), Lifecycle.State.RESUMED);


    return view;
}
Alma N
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 22 '23 at 15:02
0

Adding to the above answers, I prefer creating an extension function to reduce boilerplate code:

Extension function implementation:

fun Fragment.setupMenu(@MenuRes menuId: Int, onMenuSelected: (MenuItem) -> Boolean) =
(requireActivity() as MenuHost).addMenuProvider(object : MenuProvider {
    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) =
        menuInflater.inflate(menuId, menu)

    override fun onMenuItemSelected(menuItem: MenuItem) = onMenuSelected(menuItem)
}, viewLifecycleOwner, Lifecycle.State.RESUMED)

This way, you will always insure you are adding the lifeCycleOwner and passing the correct lifecycle state which is Lifecycle.State.RESUMED

And you can simply use this inside fragment calling:

setupMenu(R.menu.menu_save) { menuItem ->
    when (menuItem.itemId) {
        R.id.save -> {
            viewModel.onSaveClicked()
            true
        }
        else -> false
    }
}
Salam El-Banna
  • 3,784
  • 1
  • 22
  • 34