I have a menu item system implemented into my action bar, I am trying to get a menu item to become invisible once clicked and then to become visible again after clicking a different menu item.
The reason for this is to prevent a menu item getting clicked more than once.
I have done the following attempt below however, I get a null pointer exception error. I believe I may need to implement some if statements here but I'm not sure how to go about it.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.tasklistmenu,menu);
return true;
}//OnCreateOptionsMenu
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.allTasks:
item.setVisible(false);
MenuItem notStartedStatus = findViewById(R.id.notStartedStatus);
notStartedStatus.setVisible(true);
MenuItem inProgressStatus = findViewById(R.id.inProgressStatus);
inProgressStatus.setVisible(true);
MenuItem completeStatus = findViewById(R.id.completeStatus);
completeStatus.setVisible(true);
Log error
Attempt to invoke interface method 'android.view.MenuItem
android.view.MenuItem.setVisible(boolean)' on a null object reference
NEW CODE
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem allTasks = menu.findItem(R.id.allTasks);
MenuItem notStartedStatus = menu.findItem(R.id.notStartedStatus);
MenuItem completeStatus = menu.findItem(R.id.completeStatus);
MenuItem inProgressStatus = menu.findItem(R.id.inProgressStatus);
if(tasksIsVisible) {
allTasks.setVisible(true);
} else {
allTasks.setVisible(false);
}
if(notStartedIsVisible) {
notStartedStatus.setVisible(true);
} else {
notStartedStatus.setVisible(false);
}
if(completeIsVisible) {
completeStatus.setVisible(true);
} else {
completeStatus.setVisible(false);
}
if(inProgressIsVisible) {
inProgressStatus.setVisible(true);
} else {
inProgressStatus.setVisible(false);
}
return true;
}//OnPrepareOptions
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.allTasks:
tasksIsVisible = false;
invalidateOptionsMenu();
Its not enough to change the isVisible variable. You have to call the setVisible() method every time you want to change the visibility. That method does more than just setting a boolean value, so just changing a boolean value will not do.
After changing the isVisible value to false, you need to call invalidateOptionsMenu() which will re-launch the menu by calling onPrepareOptionsMenu() again.
public boolean onPrepareOptionsMenu(Menu menu)
{
MenuItem notStartedStatus = menu.findItem(R.id.notStartedStatus);
if(isVisible)
{
notStartedStatus.setVisible(true);
}
else
{
notStartedStatus.setVisible(false);
}
return true;
}
Try this code for making the menu items unvisible:
...
isVisible = false;
invalidateOptionsMenu();
...
Related
I want to update my UI based on the ongoing Selected Item from the BottomNavigationView. The problem is that the item ID will be changed after the whole method inside the listener is executed, and method getSelectedItemId() will refer to the item that was selected before when I called it inside updateUI() method.
How I can work around this?
Here is the code:
mBottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_baby:
case R.id.navigation_you:
case R.id.navigation_partner: {
updateUI();
return true;
}
default:
return false;
}
}
});
Update Method:
The value of mBottomNavigationView.getSelectedItemId() refer to the previous selection
private void updateUI() {
mBagListAdapter.setList(mPregnancyLab.getBagList(mBottomNavigationView.getSelectedItemId()));
mBagListAdapter.notifyDataSetChanged();
}
Try this
mBottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
mBagListAdapter.setList(mPregnancyLab.getBagList(item.getItemId()));
BagListAdapter.notifyDataSetChanged();
}
});
Hope this helps. Feel free to ask for clarifications...
I will create an app that user will be enter the website url to my app. Then I am showing this page in my app using WebView.
As you know, when user clicks the any text in the context a little bit long, android cursor will appear then we can select text as many as we want.
After selection, we will see that "COPY, SHARE, SELECT ALL" etc..
My question is that when user selects text, I want to show them different options. Let's say "MyCOPY, SendTwitter, SendMessage".
How can i do that?
What I did so far?
I am just creating bar at the top of the app. But I don't want this.
Here is the code:
private WebView view;
private final String TAG = MainActivity.class.getSimpleName();
private ActionMode actionMode;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.view = findViewById(R.id.webView);
view.loadUrl("https://stackoverflow.com/questions/28385768/android-how-to-check-for-successful-load-of-url-when-using-webview-loadurl");
view.setWebViewClient(new MyWebViewClient());
Log.d(TAG, view.getUrl());
view.setOnLongClickListener((v) -> {
if (actionMode != null)
return false;
actionMode = startSupportActionMode(actionCallBack);
return true;
});
}
Where startSupportActionMode(actionCallBack) is
private ActionMode.Callback actionCallBack = new ActionMode.Callback() {
#Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
actionMode.getMenuInflater().inflate(R.menu.custommenu, menu);
actionMode.setTitle("Choose");
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
switch (menuItem.getItemId()){
case R.id.example_item_1:
Toast.makeText(MainActivity.this, "Option 1 selected", Toast.LENGTH_SHORT).show();
actionMode.finish();
return true;
case R.id.example_item_2 :
Toast.makeText(MainActivity.this, "Option 2 selected", Toast.LENGTH_SHORT).show();
actionMode.finish();
return true;
default:
return false;
}
}
#Override
public void onDestroyActionMode(ActionMode actionMode) {
actionMode = null;
}
};
You can implement the ActionMode.Callback interface to create your own menu upon selection.
An action mode's lifecycle is as follows:
onCreateActionMode(ActionMode, Menu) once on initial creation
onPrepareActionMode(ActionMode, Menu) after creation and any time the
ActionMode is invalidated
onActionItemClicked(ActionMode, MenuItem)
any time a contextual action button is clicked
onDestroyActionMode(ActionMode) when the action mode is closed
just make sure that your text views allow for text selection (android:textIsSelectable="true")
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
// Called when the action mode is created; startActionMode() was called
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
return true;
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_share:
shareCurrentItem();
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
// Called when the user exits the action mode
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
};
then call startActionMode() to enable the contextual action mode when appropriate (source), such as inside a setOnLongClickListener
I have been trying to find the source of ActionMode memory leak for days now without luck. I have an activity with several fragments and when I leave the fragment having ActionMode (while auto cancelling it), LeakCanary detects a memory leak.
I have nulled both ActionMode and ActionMode.Callback on destroy() and even tried doing it on onDestroyActionMode().
Here is my LeakCanary screenshot:
https://i.imgur.com/RUbdqj3.png
I hope someone points me in the right direction.
P.S. I have suspected it has something to do with ActionMode.Callback. Though, I could not find any methods for the CallBack that destroys it. I start the ActionMode using startSupportActionMode(mActionModeCallback). I have tried to find a method to remove the mActionModeCallback from that, too, but no methods.
Here is my full ActionMode code:
private ActionMode mActionMode;
private ActionMode.Callback mActionModeCallback;
public void startCAB()
{
if (mActionMode == null)
mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(mActionModeCallback);
}
private void buildActionModeCallBack()
{
mActionModeCallback = new ActionMode.Callback() {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_cab, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
... Some Code ...
}
}
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
mActionModeCallback = null; // Tried with and without this.
}
};
}
public void finishActionMode()
{
mActionMode.finish();
}
#Override
public void onDestroy()
{
super.onDestroy();
mActionMode = null;
mActionModeCallback = null;
}
Parent Activity containing fragments:
#Override
public void onTabUnselected(TabLayout.Tab tab)
{
clearCAB();
}
private void clearCAB()
{
int index = mPagerAdapter.getCurrentFragmentIndex();
FragmentOne fragmentOne = (FragmentOne) mPagerAdapter.instantiateItem(mViewPager, index);
fragmentOne.finishActionMode();
}
According to my experience, if your ActionMode.Callback object use the Anonymous inner class it may cause your fragment memory leak.
Maybe you can create a new class and implements ActionMode.Callback then use it to put in startSupportActionMode() parameter:
public class YourFragment extends skip implements skip, ActionMode.Callback {
private ActionMode mActionMode;
public void startCAB()
{
if (mActionMode == null)
mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new SafeActionModeCallback(this));
}
public void finishActionMode()
{
mActionMode.finish();
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_cab, menu);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
// ... Some Code ...
}
}
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
}
SafeActionModeCallback:
public class SafeActionModeCallback implements ActionMode.Callback {
// you can also use the WeakReference
private ActionMode.Callback callback;
public SafeActionModeCallback(ActionMode.Callback callback) {
this.callback = callback;
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return callback.onCreateActionMode(mode, menu);
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return callback.onPrepareActionMode(mode, menu);
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return callback.onActionItemClicked(mode, item);
}
#Override
public void onDestroyActionMode(ActionMode mode) {
callback.onDestroyActionMode(mode);
callback = null;
}
}
It seems the ActionMode in the activity has a reference to the fragment's layout which is causing the memory leak and preventing the fragment from getting GC'ed. I couldn't find a way to remove the reference.
In my use case, I'm using a ListView inside the fragment that was activating the activity's ActionMode (via listener.setMultiChoiceModeListener).
My hacky solution: In the fragment's onDestroyView, remove the listView (or whichever view activated the ActionMode) from the layout and remove all listeners for the list view. I made a kotlin extension method for it:
fun ListView.removeViewAndClearListeners() {
setMultiChoiceModeListener(null)
setOnScrollListener(null)
onItemClickListener = null
(parent as? ViewGroup)?.removeView(this)
}
After doing this, the leak is gone.
I am still wondering why you are relying on ActionMode.Callback. I had an application where I was supposed to create a Custom Menu on long press and I wasted almost 2 months on this issue :
ActionModeCallback does not work
I am not sure If you are aware of this or not, The ActionMode Callback barely works on all devices. After a lot of research, I came to know that devices who are focusing too much on battery consumption and optimization will not let your background services and some callbacks work as expected.
Try testing your code on MI or Oppo/Vivo devices. It will jump directly to onDestroyActionMode instead of calling onActionItemClicked
I tried this solution, but it doesn't work as I expected. Here is my code, and this is what i have tried.
PopupMenu popup = new PopupMenu(TableActivity.this, view);
popup.setOnMenuItemClickListener(TableActivity.this);
menu = popup.getMenu();
popup.inflate(R.menu.popup_shift);
popup.show();
popup.setOnMenuItemClickListener(this);
#Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_merge:
if(tableDbList.get(positionShift).getMergeTableId()== 0) {
//this is the condition to show/hide popup menuitem
popup.getMenu().findItem(R.id.menu_merge).setVisible(false);
}else {
checkPinCode.checkPinCodemethod(TableActivity.this, "mergeCancel");
}
}
return true;
}
You are trying to change visibility on MenuItem click . It will work but popupMenu will dismiss just after click . So it does not make any sense.
If your requirement is to show items on some condition you should set visibility before show(). Below is a simple example .
private void showPopup() {
final PopupMenu popup = new PopupMenu(MainActivity.this, view);
popup.getMenuInflater().inflate(R.menu.popup_shift, popup.getMenu());
if(someCondition){
popup.getMenu().findItem(R.id.menu_merge).setVisible(false);
}
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
Toast.makeText(MainActivity.this, "You Clicked : " + item.getTitle(), Toast.LENGTH_SHORT).show();
return true;
}
});
popup.show();
}
I have implemented the Android search widget in my navigation drawer based app. I have set it to open the keyboard and focus the editText when clicking on the search icon. I want to set the back button (up button) to hide the keyboard. I have searched the web for the R.id of the up button, and found this android.R.id.home. So I have set it to be:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
...
case android.R.id.home:
hideKeyboard();
break;
...
}
I debugged the code and noticed that clicking on the navigation bar icon fires up the android.R.id.home, but hitting the up button of the search widget doesn't even enter the onOptionsItemSelected(MenuItem item) function.
I have also tried this:
searchView.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
hideKeyboard();
}
}
});
But didn't work.
How can I hide the keyboard when pressing the back (up) button?
Setting the search view:
private void setSearchView(Menu menu) {
// Get the SearchView and set the searchable configuration
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchView = (SearchView) menu.findItem(R.id.search).getActionView();
// Assumes current activity is the searchable activity
searchView.setSearchableInfo(searchManager
.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
SearchView.OnQueryTextListener queryTextListener = new SearchView.OnQueryTextListener() {
public boolean onQueryTextChange(String newText) {
Home.getFilter().filter(newText);
return true;
}
public boolean onQueryTextSubmit(String query) {
return true;
}
};
searchView.setOnQueryTextListener(queryTextListener);
}
The following code should work:
searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setOnCloseListener(new OnCloseListener() {
#Override
public bool OnClose() {
searchView.clearFocus();
return true;
});
However this didn't work for me for some reason. :-/
I found the workaround below here:
searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
#Override
public void onViewDetachedFromWindow(View v) {
searchView.clearFocus();
}
#Override
public void onViewAttachedToWindow(View v) {
}
});
I don't think that using android.R.id.home will work since I think that onOptionsItemSelected(android.R.id.home) will only be called once the SearchView has been closed.