Reputation: 977
I want to show/hide a menu option on the top bar programmatically from a fragment.
There are two possible situations:
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.
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:
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):
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
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
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