findViewById for MenuItem returns null - android

This is my xml file for the ActionBar menu.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/fav_button"
android:title="Favourite"
android:icon="#drawable/unstar"
android:showAsAction="always|withText" />
</menu>
In my onCreate function, after calling setContentView. I do favButton = (MenuItem) this.findViewById(R.id.fav_button); But this returns null.
But returns the proper object on the onOptionsItemSelected function.
I'm using ActionBarSherlock, if that would make a difference.
I have tried various options suggested by other findViewById returns null questions, but they haven't solved my issue.

Instead of
favButton = (MenuItem) this.findViewById(R.id.fav_button);
in onCreateOptionsMenu after getMenuInflater().inflate(R.menu.activity_main, menu);
favButton = menu.findItem(R.id.fav_button);

Use menu.findItem() to get the menu. But this needs to be done after the menu is inflated.
Also, to answer your q in comment, you could use onPrepareOptionsMenu to set the state of your menu. If this menu is a one time updating, you could use onCreateOptionsMenu too, which is called only once.

but if someone really needs View and not MenuItem (for different manipulations, for example to start animation) you can still get it the next way:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.your_menu_xml_file, menu);
...
new Handler().post(new Runnable() {
#Override
public void run() {
view = findViewById(R.id.menu_refresh_button);
// view.startAnimation(animation);
}
});
return true;
}

Try
final Toolbar toolbar = findViewById(R.id.toolbar);
Menu menu=toolbar.getMenu();
MenuItem item = menu.findItem(R.id.'name id item');
for me works.

I don't know why, but in my case, I use findViewById(R.id.menu_id) return null. But I find that I use findViewById(item.getItemId) in onOptionsItemSelected, it return the view what we want.

Related

When to call findViewById with menu item id to ensure it is not null?

I'm inflating the menu and trying to find the view of one of the menu items in the following way:
#Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
// will print `null`
Log.i("TAG", String.valueOf(findViewById(R.id.action_hello)));
return true;
}
In the result null is printed in Logcat. However if I add some delay before calling findViewById, it returns correct View object:
#Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(final Void... voids) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(final Void aVoid) {
// will print correctly android.support.v7.view.menu.ActionMenuItemView...
Log.i("TAG", String.valueOf(findViewById(R.id.action_hello)));
}
}.execute();
return true;
}
Of course this solution is very dirty and the minimal delay is unknown. Is there a way to register some callback for the menu inflated event. In other words: how can I call findViewById with the menu item id to be sure that the view is already there and this call won't return null?
Just override public void onPrepareOptionsMenu(Menu menu).
Documentation says:
This is called right before the menu is shown, every time it is shown.
You can use this method to efficiently enable/disable items or
otherwise dynamically modify the contents.
The view for Menu is created after calling onCreateOptionsMenu(Menu), that's why you can't access it subviews.
There are two ways to find the menu item view.
Frist way:-
Add a actionViewClass in your menu item, so that you can get view returned by getActionView. As getActionView() only works if there's a actionView defined for menuItem.
Add this in your menu item xml:-
<item
android:id="#+id/menuAdd"
android:icon="#android:drawable/ic_menu_add"
android:title="Add"
app:showAsAction="always"
app:actionViewClass="android.widget.ImageButton" />
In onCreateOptionsMenu method:-
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_item,menu);
View menuView=menu.findItem(R.id.menuAdd).getActionView();
Log.e("TAG", "onCreate: "+menuView );
return true;
}
Second way:-
The second way is to use a handler. Using this method you won't need to specify the time for the delay. Check the answer given by #davehenry here
You must call the super method when your code is finished or things just don't work as expected.
#Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
super.onCreationOptionsMenu(menu);
// calling the super completes the method now you code.
Log.i("TAG", String.valueOf(findViewById(R.id.action_hello)));
return true;
}
This way you get the menu item's id and its actionview:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
MenuItem mi = menu.findItem(R.id.action_hello);
int id = mi.getItemId();
Log.i("TAG", String.valueOf(id));
View actionView = mi.getActionView();
if (actionView == null) {
Log.i("TAG", "ActionView is null");
} else {
Log.i("TAG", "ActionView is NOT null");
}
return true;
}
Posting Runnable to the Handler queue would usually have that Runnable executed after the main UI thread finished with the currently being executed method part of the Activity's lifecycle, hence it would be a good chance to get what you want there. I do need to note that it's a trick which could fail if underestimated and not well tested but it has worked for me ever since I figured it out.
#Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
new Handler(getMainLooper()).post(new Runnable() {
#Override
public void run() {
Logger.LogI(TAG, "run: " + findViewById(R.id.action_hello));
}
});
return true;
}
Create a global variable for future use:
private ImageButton actionHelloView;
Then, in your onCreateOptionsMenu:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_main, menu);
actionHelloView = (ImageButton) menu.findItem(R.id.action_hello).getActionView();
Log.i("the view is: ", String.valueOf(actionHelloView));
return true;
}
Put this in your XML:
<menu 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"
tools:context="com.test.teststack.MainActivity">
<item
android:id="#+id/action_hello"
android:title="#string/action_settings"
app:showAsAction="never"
app:actionViewClass="android.widget.ImageButton"/>
</menu>
Note:
depending on your API version, you can switch between app:actionViewClass and android:actionViewClass in your xml.
Result in LOGCAT:
07-25 17:55:07.138 9491-9491/? I/the view is:: android.widget.ImageButton{5542a5c VFED..C.. ......I. 0,0-0,0 #7f080011 app:id/action_hello}
You don't mention why you want to find the menu item view and that may have some bearing on the answer that you are looking for. However, if you want to use findViewById() to find a menu view then this is one way to do it. The following example just changes a menu icon from an "X" to a check mark.
ViewTreeObserver.OnGlobalLayoutListener will be invoked right after layout of the toolbar in the following code. It is along the same lines as your delay, but it is the acceptable way to do this type of processing.
Alternately, the program can invoke menu.findItem(R.id.action_hello) in onPrepareOptionsMenu(). Unfortunately, the toolbar is not fully formed at this point, so a findViewById() will fail.
MainActivity.xml
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setTitle("");
toolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
ActionMenuItemView view = toolbar.findViewById(R.id.action_hello);
if (view != null) {
// onGlobalLayout may be called before toolbar is fully defined.
Log.d("onGlobalLayout", "<<<<view is not null");
// Uncomment this view to make the change to the icon here. Android Studio
// will complain about a library group, but that can be ignored for this demo.
// view.animate() might be a better demo here.
view.setIcon(getResources().getDrawable(R.drawable.ic_check));
toolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Uncomment the following line to change the icon here.
// menu.findItem(R.id.action_hello).setIcon(getResources().getDrawable(R.drawable.ic_check));
return true;
}
}
main.xml
<?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/action_hello"
android:icon="#drawable/ic_x"
android:title="Item1"
app:showAsAction="ifRoom" />
</menu>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layoutDirection="ltr"
android:padding="0px"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:contentInsetEnd="0px"
app:contentInsetEndWithActions="0px"
app:contentInsetLeft="0px"
app:contentInsetRight="0px"
app:contentInsetStart="0px"
app:contentInsetStartWithNavigation="0px"
app:logo="#null"
app:title="#null"
app:titleMargin="0px"
app:titleTextColor="#757575"
tools:ignore="UnusedAttribute"
tools:title="toolbar">
</android.support.v7.widget.Toolbar>
</FrameLayout>
In onCreateOptionsMenu(), when you call
findViewById(R.id.action_hello)
this searches in the View hierarchy starting with your "content root". Since the menu you inflated hasn't been attached to the content root yet, it is likely this will return null.
You should be able to post a Runnable to a Handler that will find the View you want. This should be called after you return from onCreateOptionsMenu() and Android has attached the menu views to the content root. You shouldn't need any delay. You just need to wait until the framework has completed the creation of the options menu.
Inflating your menu is not asynchronous, so you are able to find the item exactly where you are doing so - although onPrepareOptionsMenu is probably the more correct place to do so.
What you cannot do is use findItemById, which looks in the currently showing layout (not your collapsed menu), instead you must use menu.findItem() (or menu.getItem())
If you really need to work with the view of the Item (vs the MenuItem object) you can use menu.findItem().getActionView()

Changing menu items on a toolbar menu made from a xml layout

So I have this toolbar in my app, and I want to display different menu items depending on whether the user is logged in or not. When the user logs in or out I want to update my layout which for now is the toolbar menu to represent the change. For some reason though, my menu items are not removed at all, all of them are visible at all times, regardless of the login state.
My menu items:
<menu
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_add"
android:icon="#drawable/ic_action_add"
android:title="#string/action_add"
app:showAsAction="ifRoom"/>
<item
android:id="#+id/action_login"
android:icon="#drawable/ic_action_logged_out"
android:title="#string/action_log_in"
app:showAsAction="always"/>
<item
android:id="#+id/action_logout"
android:icon="#drawable/ic_action_logged_in"
android:title="#string/action_log_out"
app:showAsAction="always"/>
</menu>
The first menu item is only supposed to be visible to a user that is logged in. Also I guess it's self explanatory but I only want one of the log in/out buttons to be visible depending on the login state.
My Java code:
protected void initializeToolbar(){
myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
menu = myToolbar.getMenu();
resetToolbarMenu();
myToolbar.setOnMenuItemClickListener(this);
}
private void resetToolbarMenu(){
menu.clear();
getMenuInflater().inflate(R.menu.menu, menu);
if(loginPrefs.getBoolean("login", false)) { //loginPrefs is reference to a SharedPreference object
menu.removeItem(1);
} else {
menu.removeItem(2);
menu.removeItem(0);
}
}
Reason I got two methods is that I only want to set up the toolbar and listener once, but I want to be able to change the menu every time the user logs in/out without reloading the page.
After a successful login the resetToolbarMenu() method will be called again.
I suppose the menu.remove(0) does not update the UI, but I could not find another way to reach my menu object without first inflating it and then getting it from the toolbar, and I assume the inflation is what decides what items are visible. Basically, I could not find a way to remove any menu items before inflating or updating the UI in another way than inflating.
Solution:
I changed my java code into something like this:
protected void initializeToolbar(){
myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(myToolbar);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu); // Don't know if this is necessary or if returning true is prefered.
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
if(loginPrefs.getBoolean("login", false)) {
menu.getItem(0).setVisible(true);
menu.getItem(1).setVisible(false);
menu.getItem(2).setVisible(true);
} else {
menu.getItem(0).setVisible(false);
menu.getItem(1).setVisible(true);
menu.getItem(2).setVisible(false);
}
return true;
}
I also had to call invalidateOptionsMenu() on logout/login to update the toolbar/menu. However onPrepareOptionsMenu() is also automatically called whenever you open the menu for items that aren't shown on the toolbar itself.
PS: OnCreateOptionsMenu and OnPrepareOptionsMenu will not be used unless you remember to setSupportActionBar(myToolbar) as I forgot.
You can create options menu by overriding onCreateOptionsMenu. You can create 2 xml and inflate either one of them depending on your logic. To force a redraw of the menu, you can call invalidateOptionsMenu.
For example
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
if (login) {
inflater.inflate(R.menu.login_menu, menu);
} else {
inflater.inflate(R.menu.logout_menu, menu);
}
return true;
}
And outside change the flag and force redraw
login = false; // or true
invalidateOptionsMenu();
You have to override onPrepareOptionsMenu in the activity to change the items in the menu.
From the documentation:
Prepare the Screen's standard options menu to be displayed. This is called right before the menu is shown, every time it is shown. You can use this method to efficiently enable/disable items or otherwise dynamically modify the contents.

Changing options menu icon in actionbar depending on an open Fragment

I have this item in my options menu:
<item
android:id="#+id/opt_mnu_action"
android:icon="#android:drawable/ic_dialog_info"
android:orderInCategory="1"
android:showAsAction="ifRoom"
android:title="New">
</item>
The menu itself created in main FragmentActivity. I want to change this item's icon programmatically depending on the open Fragment and, obviously, have different actions when the user hits this button. I tried several things to do that, but nothing worked. The last thing I tried was this code in my Fragment's onCreateView method:
MenuItem mi = (MenuItem) view.findViewById(R.id.opt_mnu_action);
mi.setIcon(R.drawable.ico_1);
But my app crashed. So is there a way to do that?
**UPDATE**
Here's what I'm trying to do now, all in my main main FragmentActivity:
First of all I have a MenuItem action_button; in my hierarchy view. Then in my onCreateOptionsMenu method I instantiate it:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
action_button = menu.findItem(R.id.opt_mnu_action);
return super.onCreateOptionsMenu(menu);
}
Then I created this function to change the icon according to the open tab:
public void change_action_button_icon(int tab_position)
{
switch(tab_position)
{
case 0:
action_button.setIcon(R.drawable.ico_1);
break;
case 1:
action_button.setIcon(R.drawable.ico_2);
break;
case 2:
action_button.setIcon(R.drawable.ico_3);
break;
}
invalidateOptionsMenu();
}
And I call it in my onTabSelected method:
public void onTabSelected(ActionBar.Tab tab,
FragmentTransaction fragmentTransaction) {
mViewPager.setCurrentItem(tab.getPosition());
setTab_position(tab.getPosition());
change_action_button_icon(tab.getPosition());
}
But once I start my app - it crashes. I get NullPointerException error at this line:
action_button.setIcon(R.drawable.ico_1);
My guess - it happens because the icon change was requested before the action_button was instantiated. But I don't know how to overcome it...
Use this to get a reference to the menu item:
menu.findItem(resourceId).setIcon(drawableId);
You have to put the code to change the icon in onCreateOptionsMenu().
Please refer to my example below:
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.option_menu, menu);
if (needToChangeMenuItem){
menu.findItem(resourceId).setIcon(drawableId);
}
manageMenuIcon(menu);
needToChangeMenuItem = false;
return true;
}
public void manageMenuIcon(Menu menu){
if (bluetoothIconOn){
menu.findItem(R.id.secure_connect_scan).setIcon(R.drawable.bluetoothon);
} else
menu.findItem(R.id.secure_connect_scan).setIcon(R.drawable.bluetoothoff);
if (gpsIconOn)
menu.findItem(R.id.gps).setIcon(R.drawable.gps);
else
menu.findItem(R.id.gps).setVisible(false);
if (slipAndDropIconOn)
menu.findItem(R.id.fall).setIcon(R.drawable.fall);
else
menu.findItem(R.id.fall).setVisible(false);
if (fesConnectIconOn)
menu.findItem(R.id.fesConnection).setIcon(R.drawable.fesconnect);
else
menu.findItem(R.id.fesConnection).setVisible(false);
}
public void changeMenuItem(int resId, int draId){
needToChangeMenuItem = true;
resourceId = resId;
drawableId = draId;
invalidateOptionsMenu();
}
MenuItem mi = (MenuItem) view.findViewById(R.id.opt_mnu_action);
mi.setIcon(R.drawable.ico_1);
In your Fragment's onCreateOptionsMenu, load this menu, and keep a reference to the menu item (it is not part of the fragment's view hierarchy so you can't use findViewById).
Whenerver you are ready to update the icon, use mi.setIcon(R.drawable.ico_1);
and call invalidateOptionsMenu().
UPDATED:
return super.onCreateOptionsMenu(menu);
This will actually return false, since the base implementation doesn't do that. Instead skip it, or just call super.onCreateOptionsMenu(menu); first, do your stuff and then return true.
First add actionOverFlowButtonStyle into your main theme
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:actionOverflowButtonStyle">#style/MyActionButtonOverflow</item>
</style>
Define the New style for action over flow button
<style name="MyActionButtonOverflow" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
<item name="android:src">#drawable/ic_menu</item>
</style>

Getting Switch instance inside ActionBar

I managed to put a Switch inside the action bar (as in the Wi-Fi settings).
I put the following mainmenu.xml file inside the /menu folder:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="#+id/item1"
android:titleCondensed="Service"
android:title="Service"
android:actionViewClass="android.widget.Switch"
android:showAsAction="always|withText">
</item>
After that I overrode the onCreateOptionsMenu() method in the activity, as follows:
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mainmenu, menu);
// Get widget's instance
swtService = (Switch)menu.findItem(R.id.item1).getActionView();
swtService.setOnCheckedChangeListener(this);
return super.onCreateOptionsMenu(menu);
}
Unfortunately, I can't understand when this method is called.
Here's the problem: it seems that onCreateOptionsMenu is not called even before onResume(), so a NullPointerException is thrown:
#Override
public void onResume()
{
super.onResume();
// Synchronize the switch with service's status
swtService.setChecked(ServiceHelper.isServiceStarted(this, MySystemService.class.getName()));
}
Am I missing something?
Is there another way to put a View inside the action bar?
EDIT
My target API is 17, and I don't care about lower ones. :)
Here's a shot of the application, showing the lifecycle methods called:
Thanks
Try this:
#Override
public void onPrepareOptionsMenu(Menu menu){
super.onPrepareOptionsMenu(menu);
swtService.setChecked(ServiceHelper.isServiceStarted(this, MySystemService.class.getName()));
}
#Override
public void onResume()
{
super.onResume();
this.getActivity().invalidateOptionsMenu(); // If you are using fragment
invalidateOptionsMenu(); // If you are using activity
}

Get item view from ActionBar

I have an ActionBarSherlock with one menu item in my bar. When I'm call
View item = findViewById(R.id.my_item);in activity's button onClick all works fine as expected.
But when I try to do this in onCreate or onResume or even in onPostResume it is always null. I also tryed do this in onCreateOptionsMenu(Menu menu) after inflating my menu from resource, but without any succes.
Therefore I can't understand when actionbar items created and how to catch this moment?
As it has been said here and here, getActionView returns the view that we sets in setActionView. Therefore, the only one way to customize action bar menu item described here
actually it is possible to get the view of the action item, even if it's not customized.
however, do note that sometimes action items get to be inside the overflow menu so you might get a null instead.
so, how can you do it?
here's a sample code:
public boolean onCreateOptionsMenu(final Menu menu) {
getSupportMenuInflater().inflate(R.menu.main, menu);
new Handler().post(new Runnable() {
#Override
public void run() {
final View syncItemView = findViewById(R.id.action_search);
...
this was tested when using actionBarSherlock library, on android 4.1.2 and android 2.3.5 .
another alternative is to use a more extensive way , used on the showcaseView library, here .
first of all get a menu reference as below:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.custom_menu, menu);
customMenu = menu;
return super.onCreateOptionsMenu(menu);
}
after that you can get the item you need as below
customMenu.getItem(0);

Categories

Resources