HomeFeaturedAboutContact
Android
Exploring Android MenuProvider API With Activity & Fragment | Example
Ahsen Saeed
Ahsen Saeed
September 04, 2023
3 min

Table Of Contents

01
1. The grand arrival of MenuProvider API
02
2. MenuProvider interface implementation
03
3. Integration of MenuHost in androidX ComponentActivity
04
4. Add MenuProvider In Androidx Activity
05
5. Add MenuProvider In Androidx Fragment
06
6. Extension Functions For Adding the MenuProvider to Activity | Fragment

I think it’s fair to say that the vast majority of Android Developers still don’t know much about the MenuProvider API and using the old way to add the menus to ActionBar. Currently, as I write this article, it has been over a year since the Android team deprecated the onCreateOptionsMenu and onOptionsItemSelected methods, as well as the setHasOptionsMenu method.

So, welcome to the new world of Android app development, where even the simple task of managing menus gets an upgrade with the MenuProvider API.

In this article, we’ll explore how the MenuProvider API empowers developers to seamlessly create and manage menus that adapt to various contexts within your app. Say goodbye 👋 to those static menus and hello to a new era of menu management that brings versatility and efficiency to your Android application.

1. The grand arrival of MenuProvider API

With the release of androidx.activity version 1.4.0 the ComponentActivity now implements the MenuHost Interface. This allows any component to add menu items to the ActionBar by adding a MenuProvider instance to the activity or fragment. Each MenuProvider can optionally be added with a Lifecycle that will automatically control the visibility of those menu items based on the Lifecycle.State and handle the removal of the MenuProvider when the lifecycle is destroyed.

OK, enough of this farewell of MenuProvider API let’s jump to code and see how can we implement them.

2. MenuProvider interface implementation

To catch a glimpse of what MenuProvider interface capabilities are, cast your eyes upon the subsequent code snippet, which outlines all the functions accessible within it.

/**
 * Interface for indicating that a component will be supplying
 * {@link MenuItem}s to the component owning the app bar.
 */
public interface MenuProvider {

    /**
     * Called by the {@link MenuHost} right before the {@link Menu} is shown.
     * This should be called when the menu has been dynamically updated.
     *
     * @param menu the menu that is to be prepared
     * @see #onCreateMenu(Menu, MenuInflater)
     */
    default void onPrepareMenu(@NonNull Menu menu) {}

    /**
     * Called by the {@link MenuHost} to allow the {@link MenuProvider}
     * to inflate {@link MenuItem}s into the menu.
     *
     * @param menu         the menu to inflate the new menu items into
     * @param menuInflater the inflater to be used to inflate the updated menu
     */
    void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater);

    /**
     * Called by the {@link MenuHost} when a {@link MenuItem} is selected from the menu.
     *
     * @param menuItem the menu item that was selected
     * @return {@code true} if the given menu item is handled by this menu provider,
     *         {@code false} otherwise
     */
    boolean onMenuItemSelected(@NonNull MenuItem menuItem);

    /**
     * Called by the {@link MenuHost} when the {@link Menu} is closed.
     *
     * @param menu the menu that was closed
     */
    default void onMenuClosed(@NonNull Menu menu) {}
}

Chances are, these functions seem like something you’ve encountered before. They share a pretty similar name and syntax with the functions that are no longer in use. Every function with the comments is self-explanatory. You can gain a better understanding of them once we begin using them in our app.

3. Integration of MenuHost in androidX ComponentActivity

I assure you, this is the final step before we can see a practical example of utilizing the new MenuProvider in our Android app. It’s valuable to grasp the context of what we’re about to learn and how we’re approaching it.

First, the little intro of MenuHost.

MenuHost is a class that allows you to host and keep track of MenuProvider that will supply MenuItems to the app bar.

Second, the implementation of MenuHost.

public interface MenuHost {

    /**
     * Adds the given {@link MenuProvider} to this {@link MenuHost}.
     *
     * If using this method, you must manually remove the provider when necessary.
     *
     * @param provider the MenuProvider to be added
     * @see #removeMenuProvider(MenuProvider)
     */
    void addMenuProvider(@NonNull MenuProvider provider);

    /**
     * Adds the given {@link MenuProvider} to this {@link MenuHost}.
     *
     * This {@link MenuProvider} will be removed once the given {@link LifecycleOwner}
     * receives an {@link Lifecycle.Event.ON_DESTROY} event.
     *
     * @param provider the MenuProvider to be added
     * @param owner    the Lifecycle owner whose state will determine the removal of the provider
     */
    void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner);

    /**
     * Adds the given {@link MenuProvider} to this {@link MenuHost} once the given
     * {@link LifecycleOwner} reaches the given {@link Lifecycle.State}.
     *
     * This {@link MenuProvider} will be removed once the given {@link LifecycleOwner}
     * goes down from the given {@link Lifecycle.State}.
     *
     * @param provider the MenuProvider to be added
     * @param state the Lifecycle.State to check for automated addition/removal
     * @param owner the Lifecycle owner whose state will be used for automated addition/removal
     */
    @SuppressLint("LambdaLast")
    void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner,
            @NonNull Lifecycle.State state);

    /**
     * Removes the given {@link MenuProvider} from this {@link MenuHost}.
     *
     * @param provider the MenuProvider to be removed
     */
    void removeMenuProvider(@NonNull MenuProvider provider);

    /**
     * Invalidates the {@link android.view.Menu} to ensure that what is
     * displayed matches the current internal state of the menu.
     *
     * This should be called whenever the state of the menu is changed,
     * such as items being removed or disabled based on some user event.
     */
    void invalidateMenu();
}

Once more, each method is quite clear on its own, and there’s even a comment accompanying each one.

And lastly, the androidX ComponentActivity currently integrates the MenuHost interface. This facilitates the inclusion of menu items into the ActionBar by simply introducing a MenuProvider instance to the activity. Additionally, each MenuProvider can be supplemented with an optional Lifecycle, effectively managing the visibility of these menu items according to the lifecycle’s state and managing the removal of the MenuProvider upon the lifecycle’s termination.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements MenuHost, .........

4. Add MenuProvider In Androidx Activity

Now, let’s dive into the exciting part. To integrate the MenuProvider into activities, all we have to do is employ the addMenuProvider function found within the MenuHost interface. As you recall, the ComponentActivity implements the MenuHost interface. This implies that we can make use of these methods right within the activity itself.

Here’s a quick example:

class ExampleActivity : ComponentActivity() {  // 1

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

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

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return when (menuItem.itemId) { // 4
                    R.id.settings -> {
                        // navigate to settings
                        true
                    }
                    R.id.profile: {
                        // navigate to profile
                        true
                    }
                    else -> false
                }
            }

        }, owner = this, state = Lifecycle.State.RESUMED)
        
        .....
        .....
        .....
    }
}

Going through each line step by step.

  1. We’re enhancing our example activity by incorporating ComponentActivity since it provides access to the MenuHost interface.
  2. Here, we’re kickstarting our menu using the addMenuProvider method. Just a quick reminder, if you recall, the MenuHost offers various addMenuProvider methods. For the purpose of this article, we’ll stick to the one mentioned earlier, which involves a lifecycle owner and lifecycle state. If you’re interested in more details about the addMenuProvider methods and the values they take, be sure to explore this Stack Overflow thread.
  3. We’re inflating our main_menu.xml file into the interface. Remember to replace this with the name of your actual menu file.
  4. As the user taps on a menu item, the onMenuItemSelected method springs into action. We can examine which menu item was pressed and direct the navigation accordingly.

5. Add MenuProvider In Androidx Fragment

Here is a quick demonstration of how we can include a menu provider within the fragment.

class BaseFragment : Fragment() {

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

        val menuHost: MenuHost = requireActivity()

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

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return when (menuItem.itemId) { // 4
                    R.id.settings -> {
                        // navigate to settings
                        true
                    }

                    R.id.profile: {
                        // navigate to profile
                        true
                    }

                    else -> false
                }
            }

        }, viewLifecycleOwner, Lifecycle.State.RESUMED)
    }
}

Most of what’s mentioned above remains the same, with the only difference being the acquisition of the activity instance using ‘requireActivity’ and a straightforward invocation of the ‘addMenuProvider’ method on the MenuHost instance.

Now, if you’ve grasped how to incorporate menus into your Android app using the new MenuProvider API, feel free to skip the upcoming section. Hold on 🤚, are you leaving? Aren’t you interested in extension functions for menu addition in activities or fragments? If you are, please proceed with the next step.

6. Extension Functions For Adding the MenuProvider to Activity | Fragment

Kotlin extension functions offer a powerful capability to modify the class without modifying their original structure. Consequently, I’ve created a couple of extension functions for both fragments and activities.

Extension function to add menu in android ComponentActivity.

fun ComponentActivity.addMenuProvider(
    onSelected: (MenuItem) -> Boolean,
    @MenuRes menuRes: Int,
    state: Lifecycle.State = Lifecycle.State.RESUMED
) {
    addMenuProvider(
        object : MenuProvider {
            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
                menuInflater.inflate(menuRes, menu)
            }

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return onSelected(menuItem)
            }
        }, this, state
    )
}

Extension function to add menu in androidx Fragment.

fun Fragment.addMenuProvider(
    onSelected: (MenuItem) -> Boolean,
    @MenuRes menuRes: Int,
    state: Lifecycle.State = Lifecycle.State.RESUMED
) {
    requireActivity().addMenuProvider(
        object : MenuProvider {
            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
                menuInflater.inflate(menuRes, menu)
            }

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return onSelected(menuItem)
            }
        }, viewLifecycleOwner, state
    )
}

By replacing traditional methods with its flexibility, developers can craft menus that seamlessly evolve to meet various contexts. Embracing MenuProvider empowers developers to simplify code, enhance interactivity, and usher in a new era of intuitive menu handling. As Android applications continue to evolve, integrating the MenuProvider API stands as a forward-looking strategy to meet the demands of modern app development.

I hope you guys have learnt something from this article. If you have any queries or found some wrong info in the article please do comment below and let me know.

Thanks for being here and keep reading…


Tags

#android#menu-provider#androidx-menu#menuhost#featured
Ahsen Saeed

Ahsen Saeed

Mobile Application Developer

I'm a mobile product devsigner (i.e. I consider myself as both a developer and a designer) and user experience/interface engineer. I'm an expert on the Android platform and have been recognized as it by the community.

Expertise

Android
Flutter

Social Media

twitterwebsite

Related Posts

ViewPager2
Android ViewPager2 With Tablayout And Lazy Fragments
August 19, 2022
5 min
© 2023, All Rights Reserved.

Quick Links

Advertise with usContact Us

Social Media