Search code examples
androidandroid-toolbar

Android Toolbar removes all menu items when app is closed and reopened


My goal is to have menu items that are in a Toolbar still be in that Toolbar when the app is closed and reopened (Activity destroyed and created). The menu items currently get removed when the app is closed and reopened. There are 0 items and I cannot access the items that were in the Toolbar; if I try I get the following error: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

The issue appears to be that onCreateOptionsMenu() is not being called when the app is restarted.

Here is the xml with the Toolbar:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/coordinatorLayoutActivityMain"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ActivityMain">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_main" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Here is the method I use to set up the Toolbar in onResume():

    private void setUpActionBar() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toolbar toolbar = findViewById(R.id.toolbar);
                toolbar.setTitleTextColor(getResources().getColor(R.color.colorOnPrimary));
                Drawable overflowIcon = toolbar.getOverflowIcon();
                if (overflowIcon != null) {
                    overflowIcon.setColorFilter(getResources().getColor(R.color.colorOnPrimary), PorterDuff.Mode.SRC_ATOP);
                }
                setSupportActionBar(toolbar);
            }
        });
    }

and onCreateOptionsMenu():

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_toolbar, menu);
        return true;
    }

This correctly populates the Toolbar when the app is first opened.

My goal is to be able to run the following code without the IndexOutOfBoundsException when a Fragment hosted in content_main has it's view created:

        Toolbar toolbar = ((ActivityMain) getActivity()).findViewById(R.id.toolbar);
        Menu menu = toolbar.getMenu();
        menu.getItem(ActivityMain.MENU_ACTION_ADD).setVisible(true);

How can this be done?


Solution

  • The problem is that onCreateOptionsMenu() isn't called until after a Fragment's View is created.

    This means you need to set up a BroadcastReceiver with the Fragment when it's View is created and send out a matching Broadcast via an Intent at the end of onCreateOptionsMenu().

    Here is an example. First set up the BroadcastReceiver in onViewCreated(). Shoutout to CommonsWare for making me aware of the race condition. Thank you!

        private void setUpBroadcastReceiverServiceOnOptionsMenuCreated() {
            ActivityMain activityMain = ((ActivityMain) getActivity());
            IntentFilter filterComplete = new IntentFilter();
            filterComplete.addCategory(Intent.CATEGORY_DEFAULT);
            filterComplete.addAction(activityMain.getResources().getString(
                    R.string.broadcast_receiver_on_create_options_menu));
            broadcastReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    Toolbar toolbar = activityMain.findViewById(R.id.toolbar);
                    // Menu items have been added!!!
                    Menu menu = toolbar.getMenu();
                    menu.getItem(ActivityMain.MENU_ACTION_ADD).setVisible(true);
                }
            };
            activityMain.registerReceiver(broadcastReceiver, filterComplete);
        }
    

    Create a method to send the Broadcast:

        private void sendBroadcastOnOptionsMenuCreated() {
            Intent intent = new Intent();
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setAction(getResources().getString(
                    R.string.broadcast_receiver_on_create_options_menu));
            sendBroadcast(intent);
        }
    

    Finally, send the broadcast when the menu is created.

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.menu_toolbar, menu);
            sendBroadcastOnOptionsMenuCreated();
            return true;
        }