How to create a Menu instance programmatically? i.e. inflate a Menu outside onCreateOptionsMenu

25,534

Solution 1

Here's a trick to get an instance of Menu:

PopupMenu p  = new PopupMenu(getContext(), null);
Menu menu = p.getMenu();

Solution 2

I found two solutions to programmatically create a Menu instance and inflate it:

// Creating an instance by reflection

Menu menu = newMenuInstance(context);


protected Menu newMenuInstance(Context context) {
    try {
        Class<?> menuBuilderClass = Class.forName("com.android.internal.view.menu.MenuBuilder");

        Constructor<?> constructor = menuBuilderClass.getDeclaredConstructor(Context.class);

        return (Menu) constructor.newInstance(context);

    } catch (Exception e) {e.printStackTrace();}

    return null;
}

Once you have a Menu instance you can easily inflate it from a menu XML resource anywhere in your program

new MenuInflater(context).inflate(menuId, menu);

I tested both methods and they are working perfectly, I would recommend using the second method with the standard Menu and MenuItem classes from android SDK even if your activity extends SherlockActivity because it will still receive onOptionsItemSelected(MenuItem item) regardless if you fire it with android.view.MenuItem or com.actionbarsherlock.view.MenuItem

Solution 3

I'm not sure why this isn't an answer already, and I know this is an old question, but for future readers..

If you simply do this:

val menu = MenuBuilder(context)
MenuInflater(context).inflate(R.menu.menu_XXXX, menu)

It works!

androidx.appcompat.view.menu.MenuBuilder implements android.view.Menu. Upon inspection, that's all that PopupMenu does.

Note that the com.android.internal.view.menu.MenuBuilder mentioned by @iTech and used by PopupMenu is not public and should not be used.

Here are two helper functions and a usage example:

fun Context.inflateMenu(@MenuRes menuRes: Int): Lazy<MenuBuilder> = lazy {
    MenuBuilder(this)
        .also { MenuInflater(this).inflate(menuRes, it) }
}

fun Fragment.inflateMenu(@MenuRes menuRes: Int): Lazy<MenuBuilder> = lazy {
    MenuBuilder(context)
        .also { MenuInflater(context).inflate(menuRes, it) }
}

Usage:

Activity

class MyActivity : AppCompatActivity(R.layout.activity_my) {

    val menu by inflateMenu(R.menu.menu_my)

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

        ...
    }
}

Fragment:

class MyFragment : Fragment(R.layout.fragment_my) {

    val menu by inflateMenu(R.menu.menu_my)

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

        ...
    }
}

Solution 4

You dont need to do anything with inflator in order to add your own menus. Simply override onCreateOptionsMenu and start adding your own items in the menu object. For example:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);

    menu.add(0, 1, Menu.NONE, "First");
    menu.add(0, 2, Menu.NONE, "Second");
    // repeat this to add additional menus

    return true;
}

The second argument in add method is the id. Use unique ids to identify your selected menu item.

Share:
25,534
iTech
Author by

iTech

I have B.Sc and M.Sc in computer science and working in the software industry for about 10 years, but I used to do programming long time ago since the age of Basica, GW Basic and QuickBasic :) I develop in Scala, Java, Rust as well as Android. Pofessional AWS Cloud expert, big data pipeline with large-scale production experience. Finishing my PhD in applied machine learning.

Updated on July 29, 2022

Comments

  • iTech
    iTech almost 2 years

    I want to inflate a menu object outside onCreateOptionsMenu method (which means to create/show the menu when the user doesn't press the button), so I need to create a menu instance to pass it to the inflate method.

    Here is an example of what I am trying to achieve:

    Menu menu = // How to create an instance !? 
    new MenuInflater(context).inflate(R.menu.my_menu, menu)
    

    Menu is an interface, so I need to know which class is implementing it. I did browse Android code to get any hint on how a Menu object is created, but still could not find what I am looking for.

    Edit 1

    My goal is to fire an onOptionsItemSelected(MenuItem item) event from a custom view, which will be handled by the activity, so I need to have a MenuItem object with specific itemId and title to pass it with the event.

    If I can successfully create a Menu object, it will be easy to get its children MenuItems.

    Edit 2

    I am not trying to display a menu at all, what I want is to populate a ListView with elements defined in a menu XML that have title, icon and itemId and whenever a ListViewItem is clicked I want to fire a onOptionsItemSelected(MenuItem item) event that is handled in my activity.

    I know that I can parse the menu XML to extract items information, however I will not be able to fire onOptionsItemSelected(MenuItem item) without creating a standard MenuItem object to pass it as argument.

    Any help will be appreciated. Thanks!

  • iTech
    iTech over 11 years
    I added more explanation to my question. The problem I am not trying to display a menu, I only want to create an instance in a custom view class.
  • waqaslam
    waqaslam over 11 years
    You cant do that. Because onCreateOptionsMenu is fired once when the activity is created or unless you call invalidateOptionsMenu(). So I would suggest you is to call invalidate method and then alter the menu in onCreateOptionsMenu as per your requirements.
  • iTech
    iTech over 11 years
    I am not trying to display any menus. I want to inflate a menu object in a custom view outside onCreateOptionsMenu, in order to access its children MenuItems information (e.g. title, itemId and icon). From your comment it seems this is not feasible.
  • waqaslam
    waqaslam over 11 years
    what do you mean by "inflate a menu object in a custom view"? I think View class has nothing to do with Menu, except ContextMenu.
  • iTech
    iTech over 11 years
    You are right, that's why I mentioned that I am not trying to display any menus. I added further explanation to my question. Appreciated!
  • Mike Drakoulelis
    Mike Drakoulelis over 10 years
    I tried using the second version since I don't use Sherlock, and I am getting null object returns from the newMenuInstance. Any ideas?
  • Ngo Phuong Le
    Ngo Phuong Le over 9 years
    I use AppCompat v7 library, so the first solution fit me perfectly fine. Only 1 line of code needed to add.
  • Pacerier
    Pacerier over 9 years
    @iTech, Since the constructor is not exposed publicly, how sure is it that using this method wouldn't be giving us any problems?
  • Pacerier
    Pacerier over 9 years
    @waqaslam, He means that he want to do it programmatically without needing the user to click the menu button. Your solution using onCreateOptionsMenu doesn't work because it is only called when the user presses the menu button.
  • Pacerier
    Pacerier over 9 years
    How safe is it to use reflection to achieve this functionality?
  • iTech
    iTech over 9 years
    Well, with reflection there is no guarantee but this is the case for any hidden APIs. However, I believe it is very unlikely that this will change.
  • Gábor
    Gábor over 9 years
    This seems to be much preferable to the reflection used in the accepted answer...
  • ScruffyFox
    ScruffyFox over 8 years
    This is a much nicer implementation than using reflection. This answer should be accepted
  • Ishwor Khanal
    Ishwor Khanal over 6 years
    @Musma thank you. I used in Xamarin.Android as IMenu imenu=p.Menu;