The Android design guide says that Help should always be placed as the last item of the overflow menu (it should never appear in the ActionBar) and also, that is should be present in every activity, so that users don't have to look for it. Similar approach is also recommended for Settings.
However, I'm wondering what is the best way to make sure that all the activities in my app handle these items without lots of code repetition? Putting these common items manually into every XML menu file and then manually handling clicks on each of them in every activity class is just nonsense.
Since I am already extending all of my activities from a common base class (which provides some convenience methods), I came up with this approach: In the BaseActivity class, I define an empty initOptionsMenu() method which the subclasses may override (template method pattern style) by adding their specific items to the menu. This method is called at the start of onCreateOptionsMenu() and then the base class adds the common items (settings and help) at the end of the menu.
The onOptionsItemSelected() method follows the standard pattern - it switches on the item ID and in the default case, it passes the handing to the superclass. Again, the base class handles the setting and help cases.
public class BaseActivity extends Activity {
#Override
public boolean onCreateOptionsMenu(Menu menu) {
initOptionsMenu(menu);
menu.add(Menu.NONE, R.id.menu_settings, Menu.NONE, R.string.menu_help);
menu.add(Menu.NONE, R.id.menu_help, Menu.NONE, R.string.menu_help);
return true;
}
protected void initOptionsMenu(Menu menu) {}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_help:
startActivity(new Intent(this, HelpActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Now, whenever I create a new activity, I extend the BaseActivity. If the derived activity provides some more items for the options menu, I do not override the standard onOptionsItemSelected(), but instead I override the custom initOptionsMenu() method and populate the menu there. In onOptionsItemSelected(), I need to handle only cases for the items specific for this activity, the common ones will be handled by the super call.
public class FooActivity extends BaseActivity {
#Override
protected void initOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.foo, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// cases for items in R.menu.foo
default:
return super.onOptionsItemSelected(item);
}
}
}
Is this pattern sensible? Can you come up with a better approach? Please share your thoughts.
I might not use initOptionsMenu method. I will just call super.onCreateOptionsMenu() after adding my menu from concrete implementation. My BaseActivity will add settings and help menu so
in BaseActivity:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, R.id.menu_settings, Menu.NONE, R.string.menu_help);
menu.add(Menu.NONE, R.id.menu_help, Menu.NONE, R.string.menu_help);
return true;
}
and in MainActivity extends BaseActivity:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, R.id.menu_create_dummy, Menu.NONE, R.string.menu_dummy);
menu.add(Menu.NONE, R.id.menu_delete_dummy, Menu.NONE, R.string.menu_dummy);
return super.onCreateOptionMenu(menu);
}
Related
I'm a Java programmer with experience. I started working on a small Android project and i got soooo frustrated by how much Android has complicated programming just to be more flexible.
Exactly, the problem i have is as follows: i have a menu item that i need to use in several menus inside mu app. Is there a way to create a menu item object (possible associate some function to it) and just pass it around so any activity can add it to it's own contex menu?
I have tried this:
Created a "res.menu.mymenu.xml" file that contains a menu item with the same id as menu item from another "res.menu.mymenu2.xml" file. Then i have tried this:
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(getMenuToInflate(), menu);
menu.add(0, R.id.mnhidefrom, 0, ((MenuItem)findViewById(R.id.mnhidefrom)).getTitle());
HideMenuAction.prepareAndGetHideMeMenuItem(menu, menuInfo); //creating an intent for "hide" menu item that will hold some data needed when user clicks the menu item.
}
But no luck... I got "null pointer exception" on "menu.add" line...
Is there a normal, object oriented way, to create a menu item that will know what it should do regardless of anything outside (like,say, menu it belongs to) and just pass it around like any other object and add it to any menu i like (just like i can do with Swing menu and JMenuItems)?
Ahmad has half the story. This will give you a base of menu options that are always available. The way to add menu options specific to each extending activity is to write a Fragment for it. The fragment will included in the activity layout, but you can make it take up no room or use it for padding. When a Fragment implements onCreateOptionsMenu, the choices it adds are merged with the ones that the Activity created.
Read everything to do with the options menu in this guide for more details:
http://developer.android.com/guide/topics/ui/menus.html
Is there a normal, object oriented way
Yes there is. It's called inheritance!
Create a base Activityin which you inflate the OptionsMenu once and extend this Activity for all your Activities.
public class BaseActivity extends Activity {
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.blabla:
// do something
break;
case R.id.blablab:
// do something
break;
}
return true;
}
}
Now you can do this:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
}
I used a BaseActivity as Ahmad described. It works fine.
Now I want to show a special menu item in one specific Activity (which extends the BaseActivity).
#Override
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case 0:
Intent intent = new Intent(this, BlaActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem item = menu.add(Menu.NONE, 0, 0, "Edit");
item.setIcon(R.drawable.content_edit);
return super.onCreateOptionsMenu(menu);
}
This works as I expected, but I can't get the new inserted menuitem to get in the first position.
I need it to be in first position, so it is displayed directly in the ActionBar and the user sees that there is a new option without clicking on the options button.
Edit: I just found the solution myself.
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem item = menu.add(Menu.NONE, 0, 0, "Bearbeiten");
item.setIcon(R.drawable.content_edit);
if (android.os.Build.VERSION.SDK_INT >= 11) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); //<-----
}
return super.onCreateOptionsMenu(menu);
}
I've many activities in my project and one activity for Login but I want that only show menu in all activities except in activity login, because in that menu will be an icon of end session and when press, return to the login activity. And not how, could you help me?
I would recommend that the layout for login does not have the menu and the layout of other activities if the menu has
Better to an base activity which contains your code for menus and then you can extend it your activities instead of activity.
And for login you can extend activity. so all the activities which extend baseactivity will be reflected with menu with out need to write menus code in every activity. and for the will be no menu as it does not extend base activity.
Updated:::
public class BaseActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
/* Creates the menu items */
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
/* Handles item selections */
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId())
{
case R.id.menu1:
//your stuff
break;
case R.id.menu2:
//your stuff
break;
}
return true;
}
}
Is there a convenient way of showing the same Options menu options in multiple Activities?
Example: In my app, I display a TV Guide in one of three ways.
Seven day guide (TabActivity with 7 tabs)
All channels 'Now showing' (ListActivity)
All shows today by start time (Activity - could be changed easily to ListActivity)
For the Options menu in the TabActivity, the code is quite simple...
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
menu.clear();
inflater.inflate(R.menu.gv_options_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.view:
...
...
}
}
...but at the moment it seems I need to copy/paste it to the other two Activities which I don't like doing. If I change the Options menu code for one I'll need to do it for the other two also.
The only alternative I can think of is I have a 'helper' class (POJO) to which I could add a method and pass the context into to allow use of the getMenuInflator() method and another method I could pass the result of item.getItemId() into to process with the switch-case.
What is the normal way of having multiple Activities with the same Options menu?
Create a simple separate class with these two methods:
public class MyMenuHandler {
private Activity mActivity;
public MyMenuHandler(Activity activity) {
mActivity = activity;
}
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = mActivity.getMenuInflater();
menu.clear();
inflater.inflate(R.menu.gv_options_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.view:
...
}
}
}
In your activities override those callback methods and redirect the call to an instance of your MyMenuHandler class:
public class MyActivity1 extends TabActivity {
private MyMenuHandler mMenuHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
...
mMenuHandler = new MyMenuHandler(this);
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// you may also add here some items which are specific
// for one activity, not for the others
...
return mMenuHandler.onPrepareOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// handle selection of your specific items here,
// if none of them has been selected call mMenuHandler method
...
return mMenuHandler.onOptionsItemSelected(item);
}
}
This will let you hold in one place the code which respond to selection of your basic menu items, so there will be no need to worry about copy-pasting it to all activities which are to have the same menu.
One approach is to use inheritance with your Activities. Create a base Activity that implements the options menu methods and then each child Activity will gain that functionality. This is the recommended approach on the Android developer site:
Tip: If your application contains multiple activities and some of them provide the same Options Menu, consider creating an activity that implements nothing except the onCreateOptionsMenu() and onOptionsItemSelected() methods. Then extend this class for each activity that should share the same Options Menu. This way, you have to manage only one set of code for handling menu actions and each descendant class inherits the menu behaviors.
Unfortunately this won't work for you as you are not inheriting from Activity itself but differing subclasses of it, but that is the 'normal' way to do it.
You can encapsulate your action menu in a fragment. In this way you only need to add the fragment in the onCreate menu of your activity.
You need to call setHasOptionsMenu once the fragment is created.
To add the add fragment use a tag instead of a layout id.
I have a fragment class that extends Fragment and calls setHasOptionsMenu to participate in the menu. This class also implements onCreateOptionsMenu, onPrepareOptionsMenu and onOptionsItemSelected.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
....
}
I'm dynamically loading this fragment using a FragmentTransaction in my Activity (that extends FragmentActivity).
However none of the menu callbacks (onCreateOptionsMenu, onPrepareOptionsMenu and onOptionsItemSelected) are being called (I've debugged with some breakpoints in those methods) and the menu isn't shown.
Am I missing something? Do I need to add something in my Activity?
I'm using the Android Compatibility Library, compiling with L11 SDK and testing in a Xoom.
EDIT: I've found the problem. My AndroidManifest is targeting L11, this seems to hide the menu button and prevent from the callbacks being called. However if I remove this from the manifest I loose some other features I need (for example the activated state in lists). Does anyone know how to solve this issue (enable the menu button) without removing the targetSdkVersion=11 from the Manifest?
Aromero,
Don't forget to override the onCreateOptionsMenu using the fragment version of the method, similar to this:
#Override
public void onCreateOptionsMenu (Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.queue_options, menu);
super.onCreateOptionsMenu(menu, inflater);
}
This goes in the fragment, by the way, and adds to the inflated menu of the Activity, if there is one. Had the same problem myself, until I figured this out.
Kim
If you're having this problem with ActionBarSherlock, you need to make sure your Fragments are SherlockFragments, not mere SupportFragments, and that what you're overriding is
public void onPrepareOptionsMenu (com.actionbarsherlock.view.Menu menu) {
NOT
public void onPrepareOptionsMenu (android.view.Menu menu) {
If you do the latter, you should get some sort of warning about the function being final and you being unable to override it. This is a warning that you're trying to override the wrong function!
If you fix the error by switching the class from SherlockFragment to a mere Fragment, you can create the function . . . but it won't get called.
I had the same problem, but i think its better to summarize and introduce the last step to get it working:
Add setHasOptionsMenu(true) method in your Fragment's onCreate(Bundle savedInstanceState) method.
Override onCreateOptionsMenu(Menu menu, MenuInflater inflater) (if you want to do something different in your Fragment's menu) and onOptionsItemSelected(MenuItem item) methods in your Fragment.
Inside your onOptionsItemSelected(MenuItem item) Activity's method, make sure you return false when the menu item action would be implemented in onOptionsItemSelected(MenuItem item) Fragment's method.
An example:
Activity
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.activity_menu_item:
// Do Activity menu item stuff here
return true;
case R.id.fragment_menu_item:
// Not implemented here
return false;
default:
break;
}
return false;
}
Fragment
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
....
}
#Override
public void onCreateOptionsMenu(Menu menu) {
// Do something that differs the Activity's menu here
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.activity_menu_item:
// Not implemented here
return false;
case R.id.fragment_menu_item:
// Do Fragment menu item stuff here
return true;
default:
break;
}
return false;
}
I hope this will be helpful.
Cheers.
If you have an activity and a fragment that each loads menu items then you need to take special care of which overrides you use.
Activities can override onOptionsItemSelected and onMenuItemSelected, however fragments can only override onOptionsItemSelected.
If you override onMenuItemSelected in your activity and onOptionsItemSelected in your fragment, your fragment override will never get triggered.
Instead, use onOptionsItemSelected in both activity and fragment.
You need to make sure you call setHasOptionsMenu(true); onCreate or onCreateView is called in your fragment.
You also need to implement the override of onCreateOptionsMenu inside your fragment.
Another possible case is when you use a common id for a common action in each fragment; for instance R.id.action_add
Today I had such situation: hitting the option menu [add] was invoked the "wrong" onOptionItemSelected because each fragment (replaced dynamically using a DrawerLayout) had the same R.id.action_add.
Short story, if you have such situation always check that your fragment is visible:
if (!isVisible()) return false;
Long story, pay attention at the onOptionItemSelected chain!
MainActivity
|
| onOptionItemSelected
+-----------------------
| return false
|
MyCoolFragment1
|
| onOptionItemSelected
+-----------------------
| return false
|
MyCoolFragment2
|
| onOptionItemSelected
+-----------------------
| return true
|
[item selection handled]
If you add your fragments with (something like) this:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, MyCoolFragment1.newInstance())
.commit()
and you have defined the same id for a common action (let's say R.id.action_add) in each fragment;
don't forget to add this line to each: if (!isVisible()) return false;
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!isVisible()) return false; // <-- Not visible? skip!
if (item.getItemId() == R.id.action_add) {
//.TODO whatever
return true; //.Handled!
}
return false; //.Skip
}
I had this problem when I was using the ViewPagerIndicator in conjunction with ActionBarSherlock. Although it appeared this was fixed I still ran into the problem. The work around I found was to call into the fragment manually.
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
Toast.makeText(this, "From activity", Toast.LENGTH_SHORT).show(); // TODO
Fragment currentFragment = mAdapter.getItem(mPager.getCurrentItem());
if (currentFragment != null && currentFragment instanceof SherlockFragment)
{
((SherlockFragment)currentFragment).onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
I've found the problem. The AndroidManifest is targeting SDK 11, this seems to hide the menu button and prevent from the callbacks being called. I assume that this breaks the compatibility of the menu button that seems to be replaced by the action bar in Android 3.0
I think you have implemented onCreateOptionsMenu, onPrepareOptionsMenu and onOptionsItemSelected in the class that extends Fragment. Try by doing that in your Activity class where you are loading this fragment
From the android developer site - link
Note: If you inflate menu items from a fragment, via the Fragment
class's onCreateOptionsMenu() callback, the system calls
onOptionsItemSelected() for that fragment when the user selects one of
those items. However, the activity gets a chance to handle the event
first, so the system first calls onOptionsItemSelected() on the
activity, before calling the same callback for the fragment. To ensure
that any fragments in the activity also have a chance to handle the
callback, always pass the call to the superclass as the default
behavior instead of returning false when you do not handle the item.
Therefore Marco HC is the best answer of all.
If your toolbar is defined in the parent activity xml, make sure you do this in your fragment
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
....
Toolbar toolbar = (Toolbar)getActivity().findViewById(R.id.toolbar);
((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
setHasOptionsMenu(true);
}
And then of course, override onCreateOptionsMenu like below
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.edit_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
This is the only solution that worked for me!
I had same problem and solution that worked for me is:
Remove or comment any onOptionsItemSelected() ,onMenuItemSelected() even onPrepareOptionMenu() and leave in Activity onCreateOptionsMenu() only:
#Override
public boolean onCreateOptionsMenu(Menu menu){
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.layout.menu, menu);
return true;
}
In Fragment class, in onCreateView(), put:
setHasOptionsMenu(true);
In Fragment class add :
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu,inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item){
switch(item.getItemId()){
case R.id.action_insert:
//doing stuff
return true;
}
return false;
}
Tested and worked on Android 4.4
I have 10-15 activities in my project. I want to have the option menu mostly in all Activities. Then is their any way we can do it at one place and it appears in all activities.
Also, I will like to hide the option menu in some. So, is it possible or I have to write option menu code in all activities.
Regards
Sunil
Create a Class (say BaseActivity) that extends Activity, and override onCreateOptionsMenu and onOptionsItemSelected functions.
public class BaseActivity extends Activity {
// Activity code here
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.item:
// do what you want here
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Now, in the other 15-16 activities, instead of extending an Activity, you should extend BaseActivity.
public class FooActivity extends BaseActivity {
// Activity code here
}
This way, all your activities derive the options menu. For activities where you want the options menu disabled, you can override it again in that particular activity.
public class BarActivity extends BaseActivity {
// Activity code here
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Do Nothing
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Do Nothing
}
}
Hopefully, it doesn't give you problems in the manifest file.
The solution to this problem is in your new activity add this menu method.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_home_page, menu);
menu.removeItem(R.id.logout);
return true;
}
In each activity this method will automatically appear.
If it doesn't then add it with the inflate call. It requires two parameters, an xml resource(the same one that you used in your original activity), and the menu object that is pass into the onCreateOptionsMenu method.
menu.removeItem will remove the menu item of whatever resource id you pass to it.
I hope this helps those who are facing this problem.
Thank you, and happy to share this post.
It is not enough to just extend the BaseActivity, you must also call super.onCreateOptionsMenu(menu) and super.onOptionsItemSelected(item) like this in your other activities:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
//getMenuInflater().inflate(R.menu.menu_second, menu); <- remove this
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}