I've managed to work around an issue in which an extra icon appears in the action bar after a fragment transaction, but it was a clumsy solution and I wonder if there is a better way to solve it.
I have a fragment class hierarchy in which ContentFragment
is an abstract fragment which occupies the whole screen (except for the action bar) and its subclasses contribute with additional action bar icons (by means of their respective onCreateOptionsMenu()
and onOptionsItemSelected()
methods). E.g. ContentFragmentA
contributes with icon A
, ContentFragmentB
with icon B
, ContentFragmentB
may or may not be a child class of ContentFragmentA
(if it is, then the action bar will contain both icon A
and icon B
side by side), and so on.
Initially (after the user has just logged in) the screen contains only ContentFragmentA
and the action bar has icon A
. As the user navigates through the app other content fragments (or more precisely fragment transactions) are added to the back stack and icons are correspondingly added or removed from the action bar.
It all behaves nicely until the user decides to log out which prompts the app to clear the whole back stack (bringing ContentFragmentA
back after the oldest transaction is rolled back) and immediately add a LoginContentFragment
, which contributes with a New Profile icon
to the action bar. However at this moment icon A
is also being shown beside the New Profile icon and I don't want it to be shown; that is the issue I'm facing. It should go away when the user logs out.
I solved the issue by clearing the back stack as usual and then including an extra transaction which replaces the ContentFragmentA
with a blank, icon-less content fragment with setHasOptionsMenu(false)
, so icon A
will be gone when the blank fragment is replaced with the Login fragment
. But I find this clumsy and think there might be a better way.
I have tried out calling Menu.clear()
in the ContentFragment
superclass and Activity.supportInvalidateOptionsMenu()
in the fragment replacement step but neither seems to work. Menu.clear()
in particular will just make all the icons go away leaving none in the action bar.
Does anyone know an alternative?
Relevant code:
ContentFragment.java:
public abstract class ContentFragment extends Fragment {
public static interface Callbacks {
public abstract void setCurrentContentFragment(ContentFragment contentFragment);
}
protected Callbacks mCallbacks;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onStart() {
super.onStart();
mCallbacks = (Callbacks)getActivity();
mCallbacks.setCurrentContentFragment(this);
}
@Override
public void onStop() {
super.onStop();
mCallbacks = null;
}
public boolean handleBackPressed() {
return false;
}
}
ContentFragmentA.java:
public abstract class ContentFragmentA extends ContentFragment {
protected abstract void handleIconATouched();
...
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (menu.findItem(R.id.icon_a) == null) {
inflater.inflate(R.menu.icon_a, menu);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.icon_a) {
handleIconATouched();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
}
BlankFragment.java:
public class BlankFragment extends LoggedInContentFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(false);
}
}
In MainActivity.java:
...
private void addContentFragmentNotAddingTransactionToBackStackIfCurrentFragment(ContentFragment fragment, boolean clearBackStack) {
// mCurrentContentFragment changes as the back stack is cleared, thus addToBackStack is calculated before the clearBackStack() step.
boolean addToBackStack = (false == clearBackStack && (mCurrentContentFragment != null && fragment.getClass() != mCurrentContentFragment.getClass()));
if (clearBackStack) {
clearBackStack();
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.replace(R.id.container, fragment);
ft.commit();
getSupportFragmentManager().executePendingTransactions();
}
public void clearBackStack() {
while (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStackImmediate();
}
// This is the step I would like to avoid
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, new BlankFragment());
ft.commit();
getSupportFragmentManager().executePendingTransactions();
}
...
I solved the issue. It was a problem with the fragment transaction back stack. I was mixing transactions which were added to the back stack with ones that weren't, and these were causing the back stack popping to work in an unexpected way and prevent fragments from being correctly removed so they were adding extra icons to the action bar. The issue is explained in this SO question. The solution I adopted is this one.