A. Cedano
A. Cedano

Reputation: 977

How to hide menu option programmatically in Android?

I want to show/hide a menu option on the top bar programmatically from a fragment.

There are two possible situations:

  1. In the preferences the user indicates that he does not want to use that option. In that case, the option will not appear in the top bar menu.

  2. In the preferences the user indicates that he wants to use that option. In that case, the option will appear in the top bar menu.

Regarding (1) there is no problem, the option is invisible by default.

My problem is regarding (2).

When the fragment is opened I check the status of that option in the preferences. When the user has indicated that he wants to use that option, I show it in the top bar. This is a text-to-speech reading option. And also I want to hide the option in the top bar menu when there is a reading in progress.

Everything works fine, but only the first time. That is, the first time I enter the fragment, if the option in the preferences is activated, I can see the button in the menu and I can use it without problems and I can also hide it when a voice reading is happening and show it again when the reading stops.

This is a screenshot of my top bar the first time I load the fragment:

enter image description here

But if I choose the Calendar option (another fragment) and from the Calendar I return to the previous fragment (Tercia), it doesn't work the same for me. Initially, the two options appear in the top bar but if I click on the Calendar option, the Play option is hidden and it no longer navigates to the Calendar. What should happen in this case is that it navigates to the Calendar snippet.

This is a screenshot of my top bar when I go back to the Tercia fragment from the Calendar and click on the Calendar button in the menu (the Play option disappears):

enter image description here

Another even more serious problem occurs. If I navigate to the Settings option (menu of three vertical dots) and then reopen the Tercia fragment (from which I navigated to Settings) and try to select the Calendar option, the App exits with an NPE:

java.lang.NullPointerException: Attempt to invoke interface method 'android.view.MenuItem android.view.Menu.findItem(int)' on a null object reference
    at org.my.app.ui.fragments.BreviarioDataFragment.setPlayerButton(BreviarioDataFragment.java:342)
    at org.myapp.app.ui.fragments.BreviarioDataFragment.lambda$observeIntermedia$3$org-myapp-app-ui-fragments-BreviarioDataFragment(BreviarioDataFragment.java:195)
    at org.myapp.app.ui.fragments.BreviarioDataFragment$$ExternalSyntheticLambda0.onChanged(Unknown Source:4)

How could I solve this problem?

This is the relevant code from the Tercia fragment:

public class BreviarioDataFragment extends Fragment implements TextToSpeechCallback {

    private Menu mainMenu;
    private boolean isReading = false;
    private boolean isVoiceOn;
    private StringBuilder sbReader;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {

        setHasOptionsMenu(true);
        binding = FragmentBreviarioDataBinding.inflate(inflater, container, false);
        View root = binding.getRoot();
        setConfiguration();
        observeHour();
        return root;
    }

    private void setConfiguration() {

        mViewModel =
                new ViewModelProvider(this).get(BreviarioViewModel.class);
        mTextView = binding.include.tvZoomable;

        progressBar = binding.progressBar;
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
        float fontSize = Float.parseFloat(prefs.getString("font_size", "18"));
        mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize);
        hasInvitatorio=prefs.getBoolean("invitatorio",false);
        isVoiceOn = prefs.getBoolean("voice", true);
        if (isVoiceOn) {
            sbReader = new StringBuilder(VOICE_INI);
        }
        pickOutDate();
    }

    private void observeIntermedia(int hourId, String endPoint) {
        mTextView.setText(PACIENCIA);
        mViewModel.getIntermedia(mDate, hourId, endPoint).observe(getViewLifecycleOwner(), data -> {
            progressBar.setVisibility(View.GONE);
            if (data.status == DataWrapper.Status.SUCCESS) {
                mTextView.setText(data.getData().getForView());
                if (isVoiceOn) {
                    sbReader.append(data.getData().getForRead());
                    setPlayerButton();
                }
            } else {
                mTextView.setText(Utils.fromHtml(data.getError()));
            }
        });
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        mainMenu = menu;
        inflater.inflate(R.menu.toolbar_menu, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.item_voz) {
            if (mActionMode == null) {
                mActionMode =
                        requireActivity().startActionMode(mActionModeCallback);
            }
            readText();
            isReading = true;
            requireActivity().invalidateOptionsMenu();
            return true;
        }

        NavController navController = NavHostFragment.findNavController(this);
        return NavigationUI.onNavDestinationSelected(item, navController)
                || super.onOptionsItemSelected(item);
    }

    @Override
    public void onPrepareOptionsMenu(@NonNull Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem item = menu.findItem(R.id.item_voz);
        if (isReading) {
            item.setVisible(false);
        }
    }

    private void setPlayerButton() {
        mainMenu.findItem(R.id.item_voz).setVisible(isVoiceOn);
    }

    //...

}

This is the .xml of the menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
    android:id="@+id/item_voz"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:icon="@drawable/ic_play"
    android:visible="false"
    android:title="@string/leer"
    app:showAsAction="ifRoom" />
<item
    android:id="@+id/nav_calendario"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:icon="@drawable/ic_calendar_toolbar"
    android:title="@string/all_calendar"
    app:showAsAction="ifRoom" />


    <item
        android:id="@+id/empty"
        android:icon="@drawable/ic_more_vert_black_32dp"
        android:title="@string/title_fragment_settings"
        app:showAsAction="always">

        <menu>

            <item
                android:id="@+id/nav_settings"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:icon="@drawable/ic_settings_black_32dp"
                android:title="@string/title_fragment_settings"
                app:showAsAction="ifRoom" />

        </menu>
    </item>

</menu>

I'm using Navigation Component too:

<fragment
    android:id="@+id/nav_calendario"
    android:name="org.myapp.app.ui.fragments.CalendarioFragment"
    android:label="@string/title_fragment_calendario"
    tools:layout="@layout/fragment_calendario" />


<fragment
    android:id="@+id/nav_settings"
    android:name="org.myapp.app.ui.fragments.SettingsFragment"
    android:label="@string/title_fragment_settings"
     />

For the Play option I don't navigate anywhere, but display a progress bar using ActionMode. There is no problem in that sense.

Upvotes: 1

Views: 3992

Answers (2)

Jamil Hasnine Tamim
Jamil Hasnine Tamim

Reputation: 4448

Follow this code:

 @Override
 public void onPrepareOptionsMenu(@NonNull Menu menu) {
        
        MenuItem item = menu.findItem(R.id.item_voz);
        if (isReading) {
            item.setVisible(false);
        }
       super.onPrepareOptionsMenu(menu);
  }

This solved the problem on updating the OptionsMenu without invoking the invalidateOptionsMenu()

Upvotes: 2

Khushbu Shah
Khushbu Shah

Reputation: 1683

You can set visibility of the menu from an activity instead of a fragment.

Here is a code of activity.

public class TestActivity extends AppCompatActivity {

private Menu mainMenu;
private MenuItem menuVoz;
private boolean isReading;

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_test);
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    super.onPrepareOptionsMenu(menu);
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_toolbar, menu);
    mainMenu = menu;
    menuVoz = mainMenu.findItem(R.id.item_voz);
    if (isReading) {
        menuVoz.setVisible(false);
    } else {
        menuVoz.setVisible(true);
    }
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) {

        case R.id.item_voz:
            // TODO Add your code
            readText();
            isReading = true;
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

public void updateMenu() {
    isReading = true; // Read vale from your resource
    if (isReading) {
        menuVoz.setVisible(false);
    } else {
        menuVoz.setVisible(true);
    }
}}

updateMenu() is a public method. You can call this method from Fragment of this activity by casting activity context whenever youo need to update it. For example,

((TestActivity) getActivity()).updateMenu();

Upvotes: 1

Related Questions