Search code examples
javaandroidandroid-fragmentsandroid-actionbarandroid-actionbar-compat

Make the extra action bar icon go away


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();
}

...

Solution

  • 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.