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.
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.
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.
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 ofMenuProvider
that will supplyMenuItems
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, .........
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.
ComponentActivity
since it provides access to the MenuHost
interface.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.main_menu.xml
file into the interface. Remember to replace this with the name of your actual menu file.onMenuItemSelected
method springs into action. We can examine which menu item was pressed and direct the navigation accordingly.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.
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…
Quick Links
Legal Stuff