Adding ActionBar Items From Within Your Fragments

This tutorial shows you how to add action items to the ActionBar from within your Fragments and how to disable or enable action items depending on the state of your fragment. All code works with the stock Android ActionBar object as well as with ActionBarSherlock.

Why do you want to add action items?

In a previous post you have seen that Android makes it easy to provide responsive interfaces. And given the vast distribution of devices sizes that is something you should do.

This post continues with the app, you have developed in the first posts of this series. The user has a list of items, a detail screen and an edit screen.

On a phone the startscreen simply shows a list of items. Here you have an action item to add a new item to the list:

Screen showing the ListFragment only

Screen showing the ListFragment only

The detail screen shows all the relevant details of this item and offers you to edit the item:

Screen showing the DetailFragment only

Screen showing the DetailFragment only

Now here’s what the tablet version looks like:

Top part of the tablet version containing both fragments

Top part of the tablet version containing both fragments

As you can see, it now sports both icons since it also presents both fragments. Now this app is pretty dull so far, and the detail screen even more so, so let me show you a real app. I take Timetable, an app that does this nicely.

Timetable app showing action items of multiple fragments

Timetable app showing action items of multiple fragments

In this screen the edit and trash icon are from the selected element and are only added when a lecture has been selected. The plus and the search icon though are from the ListFragment.

Adding action items programatically

As the post title implies the additional action items are inserted by the fragment. In fact not much different from how you add the items within your Activities. You use the onCreateOptionsMenu() method for it:

@Override
public void onCreateOptionsMenu(
      Menu menu, MenuInflater inflater) {
   inflater.inflate(R.menu.activity_itemdetail, menu);
}

As you can see the signature of this method is different from the one of the Activity class. With fragments your method takes a MenuInflater as second parameter. And the fragment’s onCreateOptionsMenu() method also doesn’t return a boolean value.

Even with this method in place Android still doesn’t display any additional menu items unless you explicitly tell it to call this method. To do so, you first have to call setHasOptionsMenu() within the fragment’s onCreate() method:

setHasOptionsMenu(true);

Now your fragments menu items will be displayed in the ActionBar.

Of course, if you do so, you have to stop using the same entries in your Activity. Otherwise it would look fine on any screen that makes use of your multi-pane layouts but you would end up with doubled icons on single-pane screens.

For the sample screen I have used a menu xml file containing only the about entry in the activity. And I use an xml file containing only the edit entry in the fragment. The about entry is not needed in the fragment, because it is added by the activity, no matter whether you are in single-pane or double-pane mode.

Changing the order of action items

By default Android displays the action items of your fragment behind those of the activity. If you use multiple fragments Android displays them in the order the fragments are created.

This default order is not necessarily what you want. For example it is very likely that you want to display an about item as the very last entry within your overflow-menu – no matter if fragments later on add more overflow-menu entries. Also some actions are more often used than others. You want to put them more to the left, so that, depending on the screen size, those are always visible. And which items are more important depends on which fragments are in use.

Tio help you achieve this you can make use of the android:orderInCategory attribute of the item elements.

The fragment_itemdetail.xml menu file:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
   <item
      android:id="@+id/edit_item"
      android:icon="@drawable/ic_action_edit"
      android:showAsAction="ifRoom"
      android:orderInCategory="10"
      android:title="@string/edit_item">
   </item>
</menu>

The activity_itemdetail.xml menu file:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
   <item
      android:id="@+id/about"
      android:icon="@drawable/ic_action_info"
      android:showAsAction="never"
      android:orderInCategory="9999"
      android:title="@string/about">
   </item>
</menu>

And finally the activity_itemlist.xml menu xml file:

<menu xmlns:android=
      "http://schemas.android.com/apk/res/android" >
   <item
      android:id="@+id/add_item"
      android:icon="@drawable/ic_action_add"
      android:orderInCategory="20"
      android:showAsAction="ifRoom"
      android:title="@string/add_item">
   </item>
   <item
      android:id="@+id/about"
      android:icon="@drawable/ic_action_info"
      android:showAsAction="never"
      android:orderInCategory="9999"
      android:title="@string/about">
   </item>
</menu>

The higher the number the later your action item is displayed. So use an absurdly high value for the about item. And order all other elements by importance.

Since all elements have an order value of zero by default, you have to use numbers for all action items as soon as you want to move one to the front.

android:orderInCategory="9999"

Handling events

As usual you can handle menu item clicks in the onOptionsItemSelected() method:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   // handle item selection
   switch (item.getItemId()) {
      case R.id.edit_item:
         // do s.th.
         return true;
      default:
         return super.onOptionsItemSelected(item);
   }
}

But bear in mind that the Activity’s onOptionsItemSelected() method is called first. Your fragment’s method is called only, when the Activity didn’t consume the event!

Adding and removing action items

So far I have only mentioned how to use action items, that are defined within an xml file. But that often is not sufficient.

See this screenshot of a German TV program app. Please ignore, that it’s in German, I explain what is relevant to you.

The Prime Guide app showing icons only when relevant

The Prime Guide app showing icons only when relevant

In this app you can select a program to get more information about it. If you select a program that hasn’t started yet, you can bookmark it and set an alarm for it. If on the other hand the program either has already started or ended those actions are not displayed. It simply makes no sense to add an alarm for some program that has already started. But all other icons are visible in both cases.

So how to do that? You could either use different menu xml files for a static set of action items. Or you could add or remove menu actions programatically. Since the second approach is more flexible I deal with it in the next paragraphs.

First you need to get hold of a Menu object. You can do so in the onCreateOptionsMenu() method shown above.

Now you can use this Menu object to either change the visibility of action items or to remove them from the menu entirely.

You can change the visibility like this:

if (mMenu != null) {
   mMenu.findItem(R.id.edit_item).setVisible(false);
}

The null check in the code is necessary, because of the order in which the methods of your fragment are called. Of course Android executes the lifecycle methods before finally calling onCreateOptionsMenu(). Thus your Menu object is null in the onCreateView() method, which you probably use to create event handlers.

If you want to entirely remove the action item, you can do so easily as well:

if (mMenu != null) {
   mMenu.removeItem(R.id.edit_item);
}

As you will see in the next few paragraphs, adding a menu item is slightly more cumbersome than just making it visible again. Thus if you want to toggle action items depending on the state of your fragment, I suggest to change the visibility instead of removing and re-adding the items.

So far I have only shown you how to get rid of action items. Of course you also can add new items. You can use one of the several add() methods of the Menu class. For example the following code fragment would add the item in the same way, as with the xml code for inflating it:

MenuItem item = 
      mMenu.add(Menu.NONE, R.id.edit_item, 10, R.string.edit_item);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
item.setIcon(R.drawable.ic_action_edit);

The first parameter sets the group. If you have no groups, use Menu.NONE. The second parameter sets the id you want to use. It’s the same one you later use in the onOptionsItemSelected() method to react to menu item clicks. The third parameter is the order value and the last one the title text you want to use. Remember, that Android uses the title text to display usage hints or to read out this value with a screenreader app. So never use null for this value.

As the code sample shows, you have to add the icon and the showAsAction attribute value with separate method calls later on.

I don’t know why there is no way to add a complete MenuItem object directly. To me that would be better readable. But that’s just a tiny detail :-)

Wrapping up

In this post I have shown that you can use the onCreateOptionsMenu() method to add action items related to your fragment to the existing icons of the ActionBar.

Since the default order in which Android adds these items is often not the preferred one, you can change this easily by adding the android:orderInCategory attribute to your menu items in the xml files.

Finally you have seen how to change the visibility of menu items, how to remove them completely or how to add new menu items.

Please let me know in the comments, if any questions are left open and which way you prefer to remove action items from the ActionBar. And don’t forget to plus one and share this post if you found it useful!

Share this article:

You can leave a response, or trackback from your own site.

12 Responses to “Adding ActionBar Items From Within Your Fragments”

  1. Linden says:

    great write-up!
    Pushing control of the addition of ActionBar items into Fragments is a nice idea, and works until you’ve got a situation such as caused by a ViewPager – offscreen Fragments may be created and resumed so you don’t want them adding their own actionbar items willy-nilly. Solution is to handle in an onPageChangeListener attached to the ViewPager and also set the default (for page 0) in the ViewPager’s hosting Activity’s/Fragment’s onCreate.
    For that reason, to be able to influence the ActionBar items at a wider level, and to have most of the logic in the same place I tend to handle it mostly in the Fragments’ hosting Activity. Need to track state (well) to do that though.
    Another way to update the actionbar items if/when (maybe) needed is to call invalidateOptionsMenu().

  2. Evanova says:

    I have tried this pattern. While it may seem convenient and a good design at first, it actually ends up preventing re-using your fragments and re-arrange them depending on, for example, the screen size:

    On a phone, Fragment A is displayed, while Fragment A and B are displayed.
    With your approach, you risk to mix actions that are not relevant to the context of the current screen, duplicate some other ones and in overall forces the displaying Activity to override some menu settings from Fragment A and or Fragment B.

    My belief is that the hosting activity should take care of menu interactions and define what its menu should contain, not the “child” fragment(s) into it. Also, some menu actions may require fragment A to interact in a way with fragment B and this interaction cannot be done within A or B.

    The way I see it is that the Activity acts as a controller while fragments display what the activity “tells” them to display depending on user interactions. Menu actions are part of this.

    • Chris says:

      These are very good points. Thanks for taking the time to mention them. I think the article does have application in some architectures, but if you have complex interaction between fragments, it does not make sense to go this approach. Now I just need to determine if my architecture supports this approach or dynamically changing the action buttons from the main activity better.

      The reason I searched for this was because I was already doing the latter, and it was becoming cumbersome with a check (if this fragment is visible, apply search to it, otherwise apply search to this one, etc) where I never plan to have both fragments visible together. But I had not yet considered the other action buttons that might be common or interact between other fragments I use on tablet displays. You’ve made me think more than I wanted to this morning, but that’s not a bad thing. :)

      • The previous poster and you are right in that the approach mentioned here does not work everywhere. It has even very severe drawbacks in conjunction with the app drawer (see my StackOverflow answer for why it’s not good use with the nav drawer).

        I still like this apporach and try to use it wherever possible.

        One solution could be to use some kind of combined approach. Where the fragment reports those menu items it needs (e.g. via an interface all fragments have to implement). The Activity has some demands of its own – for example because of some fragment combinations as Evanova suggested or because of some global needs like an “About” menu item. In the end all those menu items, that are not needed, are made invisible.

        Or you use a state machine which determines which menu resources to inflate and/or which menu items to make invisible.

  3. ms says:

    In my case, method onCreateOptionsMenu is not called.

    Even this line setHasOptionsMenu(true) was add to onCreate()

    Any idea for this problem ?

  4. Shajeel Afzal says:

    Hi! This is another great article.

    But i got a problem! I have 5 fragments in the view pager, and each fragment has its options menu, But the options menu of first Fragment works correctly but options menu of other Fragment does not work. After analyzing the code i found that onCreateOptions method is not called on some Fragments. Its happening only on Pre ICS devices.

    My StackOverflow account is blocked this time so i cant post it on it.

    Please help me i am not able to solve this issue from last two weeks.

    Thanks in advance.

  5. Janene Pappas says:

    Wolfram, thanks for posting this article, very helpful. Is the code posted somewhere for download? I looked in the BitBucket code but I guess that’s not for this tutorial.

    • The ActionViews sample project contains four fragments that all bring their own action items (or expanded layout) with them. So this project does cover some of what I’ve written in this post.

      If you look at the source, keep in mind that those four fragments inherit from a base class. This DemoBaseFragment class calls setHasOptionsMenu(true) within its onCreate() method.

      The sample though does not cover all of what I’ve written here. It does not contain any code to remove or hide any action items and doesn’t use ordering of the ActionItems.

  6. pavan says:

    Very nice tutorial you can also check this one at
    http://www.pavanh.com/2012/10/android-menu.html

  7. Simon says:

    Hello I have followed this tutorial and it was very helpful and thanks for being very clear in your steps.

    I have an issue because in my fragments, I inflate the menu XMLs and although I put the showAsAction=”always” or showAsAction=”always|ifRoom” they always go into the overflow menu, even there are no other menu items available.

    Have you experienced this?

    • Nope. I suggest to move this question to SO. Add all the relevant code snippets (fragment initialization, menu inflation, menu xml files) and maybe even screenshots of the overflow menu – after that post the link to the question here.

Leave a Reply

You can also subscribe without commenting.

Subscribe to RSS Feed My G+-Profile Follow me on Twitter!