NavigationView menu does not update - android

I am writing here about an issue that was introduced when we migrated from the AppCompat library to the AndroidX library. While doing so, we switched from android.support.design.widget.NavigationView to com.google.android.material.navigation.NavigationView and that’s when the following issue started.
In our NavigationView design, in order to save space, we implemented an expandable menu, so that when users clicks on the “more” button, the menu expands to show more options. It starts off with only some options visible, and the rest are not visible, as follows;
Option 1
Option 2
More…
Upon clicking on the “More...” button, the menu expands to;
Option 1
Option 2
Option 3
Option 4
Option 5
Option 6
To do this we used following code;
#Override
public boolean onNavigationItemSelected(MenuItem item)
{
....
if (item.getItemId() == R.id.nav_more)
{
item.setVisible(false); // hide the “More” item
getMenu().findItem(R.id.nav_option_3).setVisible(true);
getMenu().findItem(R.id.nav_option_4).setVisible(true);
getMenu().findItem(R.id.nav_option_5).setVisible(true);
getMenu().findItem(R.id.nav_option_6).setVisible(true);
return true;
}
.......
return false;
}
Well, this code has worked in the past, but when we migrated to using the androidx library, poof, it stopped working. Well, it did work a bit. The “More...” button got hidden, but the previously hidden options, were not being displayed.
As, it took me many hours to solve this issue, and to save others this headache, I will explain the issue and the solution.
The first thing to do in such cases, is to look at the source code. As the code is open source, I was able to get it at github. At first glance I didn’t get smarter. I found that the NavigationView has a NavigationMenuPresenter object field (called presenter), that has a method called updateMenuView() which calls adapter.update(), which calls prepareMenuItems() and notifyDataSetChanged(). This sounded like the needed fix, so using reflection, we accessed and called the updateMenuView() method, but surprisingly, it did not help!
So, I decided to take it to the extreme, and see what happens if I call getMenu().clear(), and believe it or not, nothing happened. It seems that any changes made to Menu after the NavigationView is shown, are ignored. But a quick look through source code, I could not see any reason for that.
So how do I solve this issue? I tried using the latest alpha version of the library, but I still have the same issue.
Well, after much work, I found the solution. It's actually simple. Just hold on for the answer.
Lionscribe

So I was back to the source code, searching for some clue, when I fell upon a method called setUpdateSuspended(boolean updateSuspended). Well, that sounded suspicious! I searched for usage of this method, and found it being called in the onClick callback. Here is a minimized version of the code;
#Override
public void onClick(View view) {
NavigationMenuItemView itemView = (NavigationMenuItemView) view;
setUpdateSuspended(true);
MenuItemImpl item = itemView.getItemData();
boolean result = menu.performItemAction(item, NavigationMenuPresenter.this, 0);
setUpdateSuspended(false);
}
Bingo! It seems that while handling clicks, the NavigationView suspends and will not recognize any changes done to menu. I am not sure the reason for this, but as we were updating the menu in the onNavigationItemSelected callback, which is called by the onClick method, the menu updates are ignored.
Well, once I understood the issue, the solution was simple and clean. I just wrapped the code in a Runnable, and posted it, so that it runs after the onClick method returns, and setUpdateSuspended is set back to false. Here is the updated code;
#Override
public boolean onNavigationItemSelected(MenuItem item)
{
....
if (item.getItemId() == R.id.nav_more)
{
final MenuItem itemFinal = item;
post(new Runnable()
{
#Override
public void run()
{
getMenu().findItem(R.id.nav_option_3).setVisible(true);
getMenu().findItem(R.id.nav_option_4).setVisible(true);
getMenu().findItem(R.id.nav_option_5).setVisible(true);
getMenu().findItem(R.id.nav_option_6).setVisible(true);
itemFinal.setVisible(false); // hide the “More” item
}
});
return true;
}
.......
return false;
}
Viola! The expandable menu now works like it used to, the hidden items are now being shown!
I hope this will be of help to others with same issue.
Lionscribe

Related

Android KitKat 4.4 TalkBack view refresh issue

I'm running my app on an Android 4.4.2 (KitKat) device.
My app has a ListView.
When I add the last item to the list view, it shows some content, and about 5 seconds later, it changes (loads a different view to that specific list item).
While TalkBack is on -
If, in that 5 seconds window, I click on the last item - the TalkBack marks it, reads it, and does not let the view change.
I do not have this issue on previous Android version.
Anyone knows why this happens? and if I can override this behavior?
Thanks!
PB
I did not find a direct solution.
I have solved the issue by overriding accessibility default behavior.
I removed the mark around the view and kept only the reading part.
This is the code I used:
view.setAccessibilityDelegate(new AccessibilityDelegate() {
#Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
//call super to perform the action
boolean ret = super.performAccessibilityAction(host, action, args);
//call super with remove-focus action to remove the mark around the view
super.performAccessibilityAction(host, AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, args);
return ret;
}
});

Android context sub-menu opened-closed-reopened by itself

I have a context menu which include a sub-menu, when-ever I tap on the item to open the sub-menu, the sub-menu opens/closes and reopens quickly. That's very annoying but more problematic some of my users don't see the sub-menu at all, it opens/closes and that's it!
Now after experimenting I figured out that long-pressing the item actually works as soon as I release the item: the sub-menu opens properly and stays open!
So I decided to build a very basic project believing I had a bug in my app, created a new app with the wizard, a single activity, a single text on which I registerForContextMenu and a context menu with a simple sub-menu.
The issue reproduced itself immediately!!! Does anyone experience the same issue and could tell me what am I doing wrong? I believe I followed documentation and samples, but I can't find any information on this problem anywhere!!!
I've posted the issue on Google groups and reported as an issue on Android project, but so far no-one responded, here are the links to both which include the test project (not sure how I can attach a file here?).
https://code.google.com/p/android/issues/detail?id=53239&can=4&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars
https://groups.google.com/forum/?fromgroups=#!topic/android-developers/SLteohmgyy0
To solve this, I had to get rid of any sub-menu in context menu and instead open another context menu on item selection.
The following got rid of the flickering and ensured the sub-menu remained open. Had to use a spare hidden view to open the new context menu though.
if (id == R.id.menu_item_for_sub_menu)
{
new Handler().postDelayed(new Runnable()
{
#Override
public void run()
{
View v = vg.findViewById(R.id.fake_view_for_context);
if (v != null)
{
registerForContextMenu(v);
openContextMenu(v);
unregisterForContextMenu(v);
}
}
}, 0);
}

InflateException: Couldn't resolve menu item onClick handler

I asked this question 6 years ago. In the meantime Android development best practices have changed, and I have become a better developer.
Since then, I have realized that using the onClick XML attribute is a bad practice, and have removed it from any code base I work on.
All of my click handlers are now defined in the code of the app, not the XML layouts!
My reasons for never using onClick are
it is easy to make a mistake in the value of the onClick XML attribute, which will then result in a run-time error
a developer might refactor the name of the click handler method, without realizing it is called from a layout (see reason 1)
finding out which method is actually being called is not always obvious. Especially if the layout is being used by a Fragment
separating the concerns of layout vs behavior is good. Using onClick mixes them up, which is bad!
I hope I have convinced you to never use onClick in a layout :) !
Below is my original question, which is a pretty good illustration of why using onClick is a bad idea.
===
I'm defining menu items in XML, and trying to use the onClick attribute that was added in API 11. When the Activity is launched in an emulator running 4.0.3, the following Exceptions occur:
FATAL EXCEPTION: main
android.view.InflateException: Couldn't resolve menu item onClick handler
onFeedbackMenu in class android.view.ContextThemeWrapper
...
Caused by: java.lang.NoSuchMethodException: onFeedbackMenu
[interface com.actionbarsherlock.view.MenuItem]
at java.lang.Class.getConstructorOrMethod(Class.java:460)
I don't understand what is causing the Exception, since the following method is defined in my Activity
import com.actionbarsherlock.view.MenuItem;
...
public void onFeedbackMenu( MenuItem menuItem ) {
Toast.makeText( this, "onFeedBack", Toast.LENGTH_LONG ).show();
}
My XML menu definition file contains:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
...
<item
android:id="#+id/menu_feedback"
android:icon="#drawable/ic_action_share"
android:showAsAction="ifRoom"
android:title="#string/menu_feedback"
android:onClick="onFeedbackMenu" />
</menu>
For backwards compatibility I am using ActionBarSherlock, and also getting a very similar Exception when I run the App on 2.3.x.
This is a more Complete version of the Stack trace
FATAL EXCEPTION: main
android.view.InflateException: Couldn't resolve menu item onClick handler
onFeedbackMenu in class android.view.ContextThemeWrapper
at com.actionbarsherlock.view.MenuInflater$InflatedOnMenuItemClickListener.<init>(MenuInflater.java:204)
at com.actionbarsherlock.view.MenuInflater$MenuState.setItem(MenuInflater.java:410)
at com.actionbarsherlock.view.MenuInflater$MenuState.addItem(MenuInflater.java:445)
at com.actionbarsherlock.view.MenuInflater.parseMenu(MenuInflater.java:175)
at com.actionbarsherlock.view.MenuInflater.inflate(MenuInflater.java:97)
...
Caused by: java.lang.NoSuchMethodException: onFeedbackMenu
[interface com.actionbarsherlock.view.MenuItem]
at java.lang.Class.getConstructorOrMethod(Class.java:460)
at java.lang.Class.getMethod(Class.java:915)
at com.actionbarsherlock.view.MenuInflater$InflatedOnMenuItemClickListener.<init>(MenuInflater.java:202)
... 23 more
I found a solution that worked for me.
Usually the onClick attribute in a layout has the following method
public void methodname(View view) {
// actions
}
On a menu item (in this case Sherlock menu) it should follow the following signature:
public boolean methodname(MenuItem item) {
// actions
}
So, your problem was that your method returned void and not boolean.
In my case, the AndroidManifest.xml of my application (kick-started by the default Eclipse assistant) contained android:theme="#style/AppTheme" in the <application> block.
When debugging the cause of the problem, it turned out that the line
mMethod = c.getMethod(methodName, PARAM_TYPES);
in android.view.MenuInflater/InflatedOnMenuItemClickListener was called with c not being my Activity class but a dubious android.view.ContextThemeWrapper (which of course doesn't contain the onClick handler).
So, I removed the android:theme and everything worked.
Although this is a bit out of date, here is the reason for the exception. When you look into the sources of android API 15 (4.0.3-4.0.4) in the class MenuInflater you will see this method:
public InflatedOnMenuItemClickListener(Context context, String methodName) {
mContext = context;
Class<?> c = context.getClass();
try {
mMethod = c.getMethod(methodName, PARAM_TYPES);
} catch (Exception e) {
InflateException ex = new InflateException(
"Couldn't resolve menu item onClick handler " + methodName +
" in class " + c.getName());
ex.initCause(e);
throw ex;
}
This is were the exception happens, as Junique already pointed out. However the removing of the app theme is just a workaround and no real option. As we see the method tries to find the Callback method on the class of the context item passed. So instead of calling getMenuInflater() in onCreateOptionsMenu you should call new MenuInflater(this), so that this is passed as a context and then the code will work.
You can still use getMenuInflater() for other api versions if you just use an if statement like this:
if (Build.VERSION.SDK_INT > 15)
inflater = getMenuInflater();
else
inflater = new MenuInflater(this);
I don't actually know if the bug happens in api versions under 15 too, so i just generally used the save version.
In my case the problem was that I had both onClick in my menu XML and an onCreateOptionsMenu in my Activity. My onClick was actually faulty (because it pointed to non-existent methods) but I didn't notice this at first because I was testing under Android 2.x, where onClick is not supported and ignored. Once I tested on 4.x though, I started getting this error.
So basically, don't use onClick if you plan on deploying under Android 2.x. It will silently ignore your onClick values until you try running on 3.0+.
I found that I had the same problem with the ActionBar menu items, and their onClick events. What i discovered is that the workstation I'm developing in had run out of memory and needed to be rebooted. The Android VM is now able to resolve the method name referenced.
Your method must accept a MenuItem as its only parameter per here.
public void onMenuItemClickMethod(MenuItem menuItem){
// Do stuff here
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.activity_main, menu);
MenuItem item = menu.findItem(R.id.menu_open);
if (item == null)
return true;
item.setOnMenuItemClickListener
(
new MenuItem.OnMenuItemClickListener ()
{
public boolean onMenuItemClick(MenuItem item)
{ return (showDirectory(item)); }
}
);
return true;
}
public boolean showDirectory (MenuItem item)
{
CheckBox checkBox = (CheckBox) findViewById (R.id.checkBox1);
checkBox.setChecked(true);
}

Overflow button forces Action Mode to finish

I have an EditText and I want the user to be able to select some text and apply some basic formatting to the selected text (bold, italic, etc). I still want the standard copy, cut, paste options to show, though. I read somewhere in the Android documentation that to do this, you should call setCustomSelectionActionModeCallback() on the EditText and pass it an ActionModeCallback(), so that's what I did. Here's my code:
In my activity's onCreate() method:
myEditText.setCustomSelectionActionModeCallback(new TextSelectionActionMode());
Callback declaration:
private class TextSelectionActionMode implements ActionMode.Callback {
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
menu.add("Bold");
return true;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
}
The problem I'm having is that when I click on the overflow button (to access my "Bold" menu item), the ActionMode gets closed immediately. If I set it to always show as an action, using this:
MenuItem bold = menu.add("Bold");
bold.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
It works fine and I can click on it (though it obviously does nothing). What am I missing here?
Edit: Just wanted to add that I run into the exact same problem if I actually inflate a menu instead of adding menu items programmatically. Once again, though, the problem goes away if I force it to always show as an action.
It's frameworks issue. If textview receive 'focus changed' event, then textview stop the action mode. When overflow popup is shown, textview miss focus.
This issue has been solved in Android 6.0. However you should use ActionMode.Callback2 as described here in Android 6.0.
For Android 5.x and below, I recommend this workaround: add a button to Toolbar or ActionBar which records the current selection and then open another context menu.
this.inputText_selectionStart = inputText.getSelectionStart();
this.inputText_selectionEnd = inputText.getSelectionEnd();
registerForContextMenu(inputText);
openContextMenu(inputText);
unregisterForContextMenu(inputText);
It is a filed Android bug: https://code.google.com/p/android/issues/detail?id=82640.
That link contains a workaround. Fortunately this has been fixed in Android 6.0.

Android OnLongClickListener strange / unreliable behaviour

I'm currently fighting against the OnLongClickListener on Android Api Lvl 8.
Take this code:
this.webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
System.out.println("long click");
return true;
}
});
It works perfectly. I can press anywhere on the WebView and the event triggers every time.
Now take a look at this one:
this.webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final EditText editText = getUrlTextField();
switch (editText.getVisibility()) {
case View.VISIBLE:
editText.setVisibility(View.GONE);
return true;
case View.GONE:
editText.setVisibility(View.VISIBLE);
return true;
default:
return false;
}
}
});
Assuming the URL EditText components is currently visible, it gets gone from the display and should be shown again when another long click event is triggered.
But if you run this, the event just works once (!) when one performs a long click on any position on the WebView. To make things complicated, the long click works again when it is performed on a link on the website...
Can anyone explain if it is a bug in the sdk and/or if there is a mistake in my thinking how the OnLongClickListener is working?!? :/
EDIT:
I've run now several different scenarios on a Nexus One and come to following conclussion: Changing the layout on runtime more or less kills the OnLongClickListener... I haven't found a way to get it work reliably at all...
I would really appreciate if anyone could give me a hint... I'm at my wits end :(
Personnally, I ended up by re-setting the listener after each relayout.
I've run into this issue as well. It seems that if the view layout changes in a way that child view bounds need to be modified (i.e. TextView is wrap_content width and you set its text to something longer/shorter than it was before), views in the hierarchy will have their onStartTemporaryDetach method called (most likely due to a layout pass, although I haven't dug deep enough to find out for sure). If you look at the source for View that onStartTemporaryDetach ultimately unsets the pressed state of the view.
Changing the views in your layout that will be updated periodically to have bounds that will not change regardless of the value you set, will fix the issue. Although, that is still not awesome.

Categories

Resources