Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save j-garin/9ce4f12e86a3b70b8c2334caaf1f8324 to your computer and use it in GitHub Desktop.
Save j-garin/9ce4f12e86a3b70b8c2334caaf1f8324 to your computer and use it in GitHub Desktop.
Android Option Menu + Fragments Workaround with ViewPager / ActionBar / Drawer, Menu Items not displaying

This gist is a workaround to this bug:

https://code.google.com/p/android/issues/detail?id=29472

No changes are needed to your current code but changing the extended fragment/activity class.

All you have to do is put this two classes in your code and make your activity extend MenuManagerActivity and your fragment MenuDelegatorFragment.

No change is needed to the actual fragment apart from the extended class. Using a Viewpager, Actionbar Tabs, Navigation Drawer (hamburger menu) on Android pre-4.4 Kitkat made the menu created from the fragments (or nested fragments) unreliable, meaning that switching between pages, changing fragments etc caused the menu not to update in sync with the currently showed fragment.

Resulting in options menu items being visible when their fragment was not or vice-versa.

This workaround delegate all the handling of the option menu to the activity that will call the fragments methods to create the menu.

The fragment side of the thing disable the framework handling and register / unregister the fragment to the activity when the fragment come in / go out from the current view, which cause an invalidate on the menu.

I don't know if work in 100% of the cases. But it works perfectly fine and reliably for me so I share it for everyone.

Let me know in comments if this worked for you. Suggestions are welcome!

import android.os.Bundle;
import android.support.v4.app.Fragment;
/**
* fragment with a workaround to handle menus in conjunction with MenuManagerActivity
*/
public class MenuDelegatorFragment extends Fragment {
private boolean mHasMenu;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void setHasOptionsMenu(boolean hasMenu) {
// Note that I'm not calling the super constructor here!
// Cause I'll delegate the menu handling to the MenuManagerActivity instead of letting
// the framework do it!
mHasMenu = hasMenu;
if (mHasMenu && getUserVisibleHint()) {
((MenuManagerActivity) getActivity()).registerFragmentForOptionMenu(this);
} else {
((MenuManagerActivity) getActivity()).unregisterFragmentForOptionMenu(this);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
boolean wasUserVisible = getUserVisibleHint();
super.setUserVisibleHint(isVisibleToUser);
if (wasUserVisible == isVisibleToUser) {
return;
}
if (mHasMenu) {
if (isVisibleToUser) {
((MenuManagerActivity) getActivity()).registerFragmentForOptionMenu(this);
} else {
((MenuManagerActivity) getActivity()).unregisterFragmentForOptionMenu(this);
}
}
}
@Override
public void onPause() {
super.onPause();
if (mHasMenu) {
((MenuManagerActivity) getActivity()).unregisterFragmentForOptionMenu(this);
}
}
@Override
public void onResume() {
super.onResume();
if (mHasMenu && getUserVisibleHint()) {
((MenuManagerActivity) getActivity()).registerFragmentForOptionMenu(this);
}
}
}
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.support.v4.app.FragmentActivity;
import java.util.HashSet;
import java.util.Set;
/**
* Activity to handle menu in a custom fashion bypassing the framework bug: https://code.google.com/p/android/issues/detail?id=29472
* To be used in conjunction with MenuDelegatorFragment.
*/
public class MenuManagerActivity extends FragmentActivity {
private boolean mOptionMenuCreated;
private Set<MenuDelegatorFragment> mRegisteredFragments = new HashSet<MenuDelegatorFragment>();
private final Handler handler = new Handler(Looper.getMainLooper());
private boolean mInvalidatePosted;
public void registerFragmentForOptionMenu(MenuDelegatorFragment fragment) {
if (mRegisteredFragments.add(fragment)) {
if (mOptionMenuCreated) {
postInvalidateOptionsMenu();
}
}
}
public void unregisterFragmentForOptionMenu(MenuDelegatorFragment fragment) {
if (mRegisteredFragments.remove(fragment)) {
postInvalidateOptionsMenu();
}
}
/**
* Method needed cause subsequents calls of {@link #invalidateOptionsMenu()} on Android pre 4.4
* will get eaten and ignored by the framework.
*/
private void postInvalidateOptionsMenu() {
if (mInvalidatePosted) {
return;
}
mInvalidatePosted = true;
handler.post(new Runnable() {
@Override
public void run() {
mInvalidatePosted = false;
invalidateOptionsMenu();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean displayMenu = super.onCreateOptionsMenu(menu);
final MenuInflater menuInflater = new MenuInflater(this);
for (MenuDelegatorFragment f : mRegisteredFragments) {
f.onCreateOptionsMenu(menu, menuInflater);
}
mOptionMenuCreated = true;
return displayMenu;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean displayMenu = super.onPrepareOptionsMenu(menu);
for (MenuDelegatorFragment f : mRegisteredFragments) {
f.onPrepareOptionsMenu(menu);
}
return displayMenu;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// this may be weird behavior: I let the call reach both the activity both any single fragment registered for option menu
boolean handled = super.onOptionsItemSelected(item);
for (MenuDelegatorFragment f : mRegisteredFragments) {
handled |= f.onOptionsItemSelected(item);
}
return handled;
}
@Override
protected void onDestroy() {
super.onDestroy();
mRegisteredFragments.clear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment