63

Within my apps I often enable/disable menu entries and do make them visible from onPrepareOptionsMenu.

Today I started to add the android:showAsAction menu attribute to some of my Android 2.x apps to show menu entries used most on the ActionBar.

The ActionBar does not reflect the enable/disable and visibility immediately. I need to click on the menu dropdown on the right to see this change happen.

Ok, I do understand that the menu fires onPrepareOptionsMenu. But what do I need to do to refresh the ActionBar? I think this change needs to be applied from within onOptionsItemSelected but I don't know what I should call.

Here's the menu:

<item
    android:icon="@drawable/ic_menu_mapmode"
    android:id="@+id/men_mapview"
    android:showAsAction="ifRoom|withText"
    android:title="@string/txt_mapview" />

<item
    android:icon="@drawable/ic_menu_mapmode"
    android:id="@+id/men_satelliteview"
    android:showAsAction="ifRoom|withText"
    android:title="@string/txt_satelliteview" />

Here's the onPrepareOptionsMenu:

@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
    MenuItem menuItemMapView = menu.findItem(R.id.men_mapview);
    MenuItem menuItemSatelliteView = menu.findItem(R.id.men_satelliteview);

    if (mapView.isSatellite()) {
        menuItemMapView.setEnabled(true).setVisible(true);
        menuItemmenuItemSatelliteView.setEnabled(false).setVisible(false);
    } else {
        menuItemMapView.setEnabled(false).setVisible(false);
        menuItemmenuItemSatelliteView.setEnabled(true).setVisible(true);
    }

    return super.onPrepareOptionsMenu(menu);
}

Here's the onOptionsItemSelected

@Override
public boolean onOptionsItemSelected(final MenuItem menuItem) {
    switch (menuItem.getItemId()) {
        case R.id.men_mapview:
            mapView.setSatellite(false);
            mapView.setStreetView(true);
            mapView.invalidate();

            invalidateOptionsMenu(); // This works on Android 3.x devices only
            return true;
        case R.id.men_satelliteview:
            mapView.setSatellite(true);
            mapView.setStreetView(false);
            mapView.invalidate();

            invalidateOptionsMenu(); // This works on Android 3.x devices only
            return true;
    }

    return super.onOptionsItemSelected(menuItem);
}

EDIT: If I add invalidateOptionsMenu this works on Android 3.x apps but crashes on Android 2.x devices because of a missing method. What's the recommended way to do it right?

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
Harald Wilhelm
  • 6,656
  • 11
  • 67
  • 85

10 Answers10

88

My method of choice is to create a helper class. For example:

class VersionHelper
{
    static void refreshActionBarMenu(Activity activity)
    {
        activity.invalidateOptionsMenu();
    }
}

Now in your code above, replace invalidateOptionsMenu(); with:

if (Build.VERSION.SDK_INT >= 11)
{
    VersionHelper.refreshActionBarMenu(this);
}

Credit for this method goes to CommonsWare (search for HoneycombHelper, and check out his books - highly recommended)

wirbly
  • 2,183
  • 1
  • 24
  • 24
  • 5
    what's the differnece between your answer and simply putting the version check around his initial call of if (Build.VERSION.SDK_INT >= 11) { invalidateOptionsMenu(); }? you could possibly stick the version check in the versionHelper method call? – topwik Aug 24 '12 at 17:17
  • 10
    @towpse The reason he has the helper class structured as it is is to avoid a crash due to non-backward compatibility. The method `invalidateOptionsMenu()` only exists in API 11 (honeycomb) and up. So if your app is to run on anything lower than API 11 it will crash. To avoid this you wrap the method in question in another **static** method (e.g. `refreshActionBarMenu()`) and **only** call this static method if you're running on API>11 (thus the version check _before_ you call the static method). This works because the `VersionHelper` class isn't loaded until you actually use it. – Tony Chan Aug 31 '12 at 21:18
  • @Turbo ok cool, thanks for the explanation , so what would one do in the else case? not update the menu at all or what's the older way of doing the menu item update if user is running an older OS? – topwik Sep 04 '12 at 20:16
  • 3
    @towpse I'm actually figuring out that situation right now. I believe that in the `else` case for this type of situation you should just do nothing. This is because the older way (pre API11) of doing the menu update is in the method `onPrepareOptionsMenu` which automatically gets called every time the user opens the menu. So to handle the older OS and the newer API>11 ones, you put the code for the menu change in `onPrepareOptionsMenu` (this is to handle old OS), then to handle newer OS you call `invalidateOptionsMenu` when an event occurs that would trigger a menu change. – Tony Chan Sep 04 '12 at 21:59
  • 1
    I the helper class protects from old SDKs, then why not do the version check inside them method? – Brill Pappin Oct 08 '13 at 21:44
  • @BrillPappin I think that was the point of CommonsWare's book. You don't need the helper if you are having the API check with the rest of your code. Unlike what @Turbo said, the app won't crash in older devices if it doesn't go inside the `if`. By using the `VersionHelper` and putting those ifs inside of it, you make your code cleaner. – Ricardo Dec 08 '13 at 08:18
68

Thanks to the accepted answer. I am using ActionBarActivity. in this class you can use

supportInvalidateOptionsMenu();
jeremyvillalobos
  • 1,795
  • 2
  • 19
  • 39
  • 4
    Give this man a cookie.... The reason why I can't set the visibility of the menu to false is that I've only use invalidateOptionsMenu(); until I found your answer. Thanks dude. – Cjames May 15 '14 at 03:27
  • 4
    This should be the selected answer. – Guy Jul 09 '14 at 08:55
  • Since actions (icons) in Toolbar are now internally represented by a TextView instead of ImageView (and I understand why), I had to manually recolor compound drawables inside the TextView, and additionally assign a TouchListener which would recolor them when pressed/activated. So basically I am coloring icons only. Anyone with a similar situation? Invalidating the menu didn't work for me.. – milosmns Mar 25 '15 at 16:20
  • 1
    this method is deprecated. – Amir Dora. Apr 28 '18 at 04:38
15

Use

ActivityCompat.invalidateOptionsMenu(Activity activity)

from the compatibility library.

See: https://stackoverflow.com/a/14748687/435855

Community
  • 1
  • 1
Urizev
  • 561
  • 6
  • 24
  • On Gingerbread, onCreateOptionsMenu() does not get called after calling this method. – bytehala Apr 24 '14 at 16:26
  • 1
    Yes. That's because preHoneycomb does not support on screen buttons and a hardware menu button is expected. So onCreateOptionMenu() method is called when user press menu button. invalidateOptionsMenu is focused on refreshing the actionbar 3.0+ supporting the old menu hardware button. – Urizev Apr 25 '14 at 01:50
11

save a reference to the menu and call:

this.menu.clear();
this.onCreateOptionsMenu(this.menu);
Klaasvaak
  • 5,634
  • 15
  • 45
  • 68
  • 3
    This is the correct answer for how to programmatically toggle visibility of Options Menu Items in the Action Bar on pre-API 11 devices. – Mike Repass Aug 30 '13 at 20:39
4

Based on "Klaasvaak" answer above. I am using its subMenus. This works for me :

// Declare and save the menu as global, so it can be called anywhere.
Menu absTopSubMenus;

public boolean onCreateOptionsMenu(Menu menu) {

absTopSubMenus = menu;  // Used for re-drawing this menu anywhere in the codes.

// The remainder of your code below
}

Then, to re-draw this, just call :

// Redraw the top sub-menu
absTopSubMenus.clear();
onCreateOptionsMenu(absTopSubMenus);
Britc
  • 623
  • 5
  • 8
4

You can now use supportInvalidateOptionsMenu() method from the ActionbarActivity with the support library. So you don't have to check for the version of the sdk. Goes until API 7 and above.

http://developer.android.com/reference/android/support/v7/app/ActionBarActivity.html

do to so, you have to import the v7 of the support library using this

http://developer.android.com/tools/support-library/setup.html

3

I am not sure you have seen it already, but if you use the actionbar extensively, and plan on supporting lower API's (>8), take a look at the actionBarSherlock library. It will make it so you do not have to section your actionBar code for lower and Higher API's, and then you can just run:

runOnUiThread(new Runnable(){
  @Override
  public void run(){
    supportInvalidateOptionsMenu();
  }
});
user1528493
  • 441
  • 4
  • 5
3

getActivity().invalidateOptionsMenu();

gets the job done.

Karun Shrestha
  • 731
  • 7
  • 16
2

Kudos to @Klaasvaak for showing us the way here. I use the following which works on both pre- and post- API Level 11:

private void invalidOptionsMenuHelper() {
    if (Build.VERSION.SDK_INT >= 11) {
        invalidateOptionsMenu();

    } else if (mOptionsMenu != null) {
        mOptionsMenu.clear();
        onCreateOptionsMenu(mOptionsMenu);
    }
}

Of course, you must save a reference to the menu (mOptionsMenu in this case) which I accomplish via:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    mOptionsMenu = menu;

    //... create and manage the menu normally
}
Mike Repass
  • 6,825
  • 5
  • 38
  • 35
0

Also make sure you are not calling

myAppCompatActivity.setToolbarTitle("some dynamic title");

shortly after you have refreshed your menu.

I had the issue that the drawables would not show up even if there was room for them to be displayed. Once I did an orientation change the drawables then appeared.. ?

In Summary:

MainActivity:

public class MainActivity extends AppCompatActivity {

    private boolean showMenu = true;

    public void setShowMenu(boolean show) {
        showMenu = show;
        supportInvalidateOptionsMenu();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.my_menu, menu);
        menu.findItem(R.id.menu_share).setVisible(showMenu);
        // menu.findItem(...
        return true;
    }
}

FragmentNoMenu:

public abstract class FragmentNoMenu extends Fragment {

    protected MainActivity mainActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        mainActivity = (MainActivity) getActivity();
        if (mainActivity != null) {
            mainActivity.setShowMenu(false);
        }
    }

}

FragmentWithMenu:

public abstract class FragmentWithMenu extends Fragment {

    protected MainActivity mainActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        mainActivity = (MainActivity) getActivity();
        if (mainActivity != null) {
            mainActivity.setShowMenu(true);
        }
    }

}
JoachimR
  • 5,150
  • 7
  • 45
  • 50