Forked from danielesegato/AndroidBug-OptionItems-Fragments-29472-workaround.md
Created
March 14, 2018 11:01
-
-
Save j-garin/9ce4f12e86a3b70b8c2334caaf1f8324 to your computer and use it in GitHub Desktop.
Revisions
-
danielesegato revised this gist
Apr 4, 2014 . 3 changed files with 166 additions and 18 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,8 +6,11 @@ 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. If you are using Nested fragment you must make sure that all fragments containing nested Fragments with options menu also extend MenuDelegatorFragment (both if the parent fragment need an option menu both if it doesn't need it). A suggestion that works very for every app: always, as first thing when you start a project, write a MyFragment and MyActivity class (or Base/Abstract whatever prefix suits you) and extend that with every single fragment/activity you write. It will be easier to handle cases like this. No change is needed to your 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**. @@ -16,6 +19,10 @@ This workaround delegate all the handling of the option menu to the activity tha 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. There's also an issue with nested fragments: they may be left resumed and with "hint visibility" = true even if the parent fragment is detached. This code also take care of that situation. There's a DEBUG flag in the fragment implementation you can use to check what's wrong if in your situation something is not working as you expect. **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! This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,27 +1,46 @@ import android.support.v4.app.Fragment; import android.util.Log; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Fragment with a workaround to handle menus in conjunction with MenuManagerActivity * <p/> * To make it work properly you need to assure that all fragments with option menu and that have nested fragments with option menu extend this fragment. * <p/> * Apparently when you detach a parent fragment the nested fragment is not detached and actually not even paused, nor the visibility hint is set to false. * For this reason I had to handle an hierarchy check between fragments making the parent fragment notify children when he is going away and coming back * and making children ask to be informed when the parent go away. * <p/> * I think I handled any fancy configuration now, not 100% sure tough. I'll keep improving if I found bugs. The nested fragment problem was an hard one to * catch and fix, I wrote a good debug log that can be enabled by just setting a variable to true. Don't worry about performance since when compiling with * that variable to false the code for logging is effectively removed, not even the if is evaluated. */ public class MenuDelegatorFragment extends Fragment { public static final String LTAG = MenuDelegatorFragment.class.getSimpleName(); private boolean mHasMenu; private final Set<MenuDelegatorFragment> mChildrenShowingMenu = new HashSet<MenuDelegatorFragment>(); private static final boolean DEBUG = false; private void debugLog(String what) { Log.v(LTAG, String.format("%s[%d] (%s): %s", mHasMenu ? "M" : "-", getId(), ((Object) this).getClass().getSimpleName(), what)); } @Override public void setHasOptionsMenu(boolean hasMenu) { if (DEBUG) { debugLog(String.format("setHasOptionsMenu(%b)", 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 && isHierarchyVisible()) { showMenu(); } else { hideMenu(); } } @@ -32,28 +51,146 @@ public void setUserVisibleHint(boolean isVisibleToUser) { if (wasUserVisible == isVisibleToUser) { return; } if (DEBUG) { debugLog(String.format("setUserVisibleHint(%b)", isVisibleToUser)); } if (mHasMenu) { if (isVisibleToUser && isHierarchyVisible()) { showMenu(); } else { hideMenu(); } } if (isVisibleToUser) { notifyChildrenToShowMenu(); } else { notifyChildrenToHideMenu(); } } @Override public void onPause() { super.onPause(); if (DEBUG) { debugLog("onPause()"); } if (mHasMenu) { hideMenu(); } notifyChildrenToHideMenu(); } @Override public void onResume() { if (DEBUG) { debugLog("onResume()"); } super.onResume(); if (mHasMenu && isHierarchyVisible()) { showMenu(); } notifyChildrenToShowMenu(); } private boolean isHierarchyVisible() { boolean visible = getUserVisibleHint() && isResumed(); Fragment parent = getParentFragment(); boolean hierarchyVisible; if (parent == null) { hierarchyVisible = visible; } else if (parent instanceof MenuDelegatorFragment) { hierarchyVisible = visible && ((MenuDelegatorFragment) parent).isHierarchyVisible(); } else { Log.w(LTAG, String.format("non-%s in the hierarchy, can't grant menu will work correctly in every situation: %s", LTAG, ((Object) parent).getClass().getSimpleName())); hierarchyVisible = visible && parent.getUserVisibleHint() && parent.isResumed(); } if (DEBUG) { debugLog(String.format("isHierarchyVisible() -> %b", hierarchyVisible)); } return hierarchyVisible; } private void showMenu() { if (((MenuManagerActivity) getActivity()).registerFragmentForOptionMenu(this)) { if (DEBUG) { debugLog("showMenu()"); } if (getParentFragment() instanceof MenuDelegatorFragment) { ((MenuDelegatorFragment) getParentFragment()).addChildShowingMenu(this); } } } private void hideMenu() { if (((MenuManagerActivity) getActivity()).unregisterFragmentForOptionMenu(this)) { if (DEBUG) { debugLog("hideMenu()"); } if (getParentFragment() instanceof MenuDelegatorFragment) { ((MenuDelegatorFragment) getParentFragment()).removeChildShowingMenu(this); } } } private void parentIsGoingAway() { if (DEBUG) { debugLog("parentIsGoingAway()"); } hideMenu(); } private void parentIsComingBack() { if (DEBUG) { debugLog("parentIsComingBack()"); } if (mHasMenu && isHierarchyVisible()) { showMenu(); } } private void removeChildShowingMenu(MenuDelegatorFragment child) { if (mChildrenShowingMenu.remove(child)) { if (DEBUG) { debugLog(String.format("removeChildShowingMenu([%s] %s)", child.getId(), ((Object) child).getClass().getSimpleName())); } } } private void addChildShowingMenu(MenuDelegatorFragment child) { if (mChildrenShowingMenu.add(child)) { if (DEBUG) { debugLog(String.format("addChildShowingMenu([%s] %s)", child.getId(), ((Object) child).getClass().getSimpleName())); } } } private void notifyChildrenToHideMenu() { if (mChildrenShowingMenu.size() == 0) { return; } if (DEBUG) { debugLog("notifyChildrenToHideMenu():" + mChildrenShowingMenu.size()); } for (MenuDelegatorFragment child : mChildrenShowingMenu) { child.parentIsGoingAway(); } mChildrenShowingMenu.clear(); } private void notifyChildrenToShowMenu() { List<Fragment> children = getChildFragmentManager().getFragments(); if (children == null || children.size() == 0) { return; } if (DEBUG) { debugLog("notifyChildrenToShowMenu():" + children.size()); } for (Fragment child : children) { if (child instanceof MenuDelegatorFragment) { ((MenuDelegatorFragment) child).parentIsComingBack(); } else if (child.hasOptionsMenu()) { Log.w(LTAG, String.format("non-%s in the hierarchy, can't grant menu will work correctly in every situation: %s", LTAG, ((Object) child).getClass().getSimpleName())); } } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,9 @@ import android.os.Handler; import android.os.Looper; import android.support.v4.app.FragmentActivity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import java.util.HashSet; import java.util.Set; @@ -19,18 +19,22 @@ public class MenuManagerActivity extends FragmentActivity { private final Handler handler = new Handler(Looper.getMainLooper()); private boolean mInvalidatePosted; public boolean registerFragmentForOptionMenu(MenuDelegatorFragment fragment) { if (mRegisteredFragments.add(fragment)) { if (mOptionMenuCreated) { postInvalidateOptionsMenu(); } return true; } return false; } public boolean unregisterFragmentForOptionMenu(MenuDelegatorFragment fragment) { if (mRegisteredFragments.remove(fragment)) { postInvalidateOptionsMenu(); return true; } return false; } /** -
danielesegato renamed this gist
Mar 28, 2014 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
danielesegato renamed this gist
Mar 28, 2014 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
danielesegato revised this gist
Mar 28, 2014 . 1 changed file with 5 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,11 @@ 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**. @@ -13,7 +18,4 @@ The fragment side of the thing disable the framework handling and register / unr **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! -
danielesegato revised this gist
Mar 28, 2014 . 2 changed files with 5 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,13 +1,17 @@ This gist is a workaround to this bug: https://code.google.com/p/android/issues/detail?id=29472 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. All you have to do is put this two classes in your code and make your activity extend MenuManagerActivity and your fragment MenuDelegatorFragment. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,3 @@ import android.os.Bundle; import android.support.v4.app.Fragment; -
danielesegato created this gist
Mar 28, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,15 @@ This gist is a workaround to this bug: https://code.google.com/p/android/issues/detail?id=29472 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. 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. 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. Let me know in comments if this worked for you. Suggestions are welcome! This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,60 @@ 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); } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,89 @@ 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(); } }