My android application targets the latest platform. I am new to the platform, and read bit conflicting information on actionbar. The way I was using it for navigation was.
menu.xml
<menu>
<item android:id="#+id/action_sort_size"
android:icon="#android:drawable/ic_menu_sort_by_size"
android:title="#string/action_barabc"
android:onClick="abc" />
<item android:id="#+id/action_sort_alpha"
....
In my activity
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void abc(MenuItem item) {
//...
}
this works, but the back/up navigation is not working correctly. could be unrelated, still like to confirm.
But, I also see implementation like here
where it switches on item.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menuitem1:
Toast.makeText(this, "Menu Item 1 selected", Toast.LENGTH_SHORT)
.show();
break;
case R.id.menuitem2:
....
}
Which is the better approach?
The better approach, in my opinion, is the switch approach. There aren't many reasons why, but I'll list them:
The code is centralized. You don't have x amount of methods that basically do the same thing. It keeps your code more readable; it is "cleaner". You also get a default statement using the switch, this can help if you mess up and forget to make a case specifically for an element in the layout.
If you really wanted to have a centralized method using xml, you would have onClick reference the same method and check the ids of the View parameter. Which is essentially the same asonOptionsItemSelected.
It is a part of the API. The Android engineers would not have made it a part of the API if they didn't want the developer to use it. Yes the XML is techinally API, but XML should be used more for layouts and visuals, not for logic.
Everyone uses it. All the tutorials I have seen and everyone's code uses this method. It is now more of a convention.
It's largely personal, but if it looks like it's a convention, and everyone uses it, I'd adhere to it. Especially if you're working as part of a team. Different coding styles for such arbritrary things should be avoided.
And concerning your back/up navigation, it shouldn't make a difference which way you do it, since you have to implement the same code to get that navigation type.
Related
I am writing UI Automation tests in Espresso for Android & came across a scenario for which I haven't got any solution so far.
In one Fragment, I have OptionsMenu with a single item. The state of that MenuItem is set according to value from API response.
#Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
getActivity().getMenuInflater().inflate(R.menu.menu_cancel_order, menu);
MenuItem cancelMenuItem = menu.findItem(R.id.cancel_order);
if(something) { // something can be a boolean value from server
cancelMenuItem.setEnabled(true);
} else {
cancelMenuItem.setEnabled(false);
}
}
For UI testing, I need to write test case for checking whether this MenuItem is enabled/disabled.
For clicking on the overflowmenu,
ViewInteraction actionMenuItemView = onView(
allOf(withId(R.id.action_settings), withContentDescription("Settings"), isDisplayed()));
actionMenuItemView.perform(click());
And so far what I have tried to check the Assertion is given below.
onView(allOf(withText("Cancel Order"), withId(R.id.cancel_order))).check(matches(not(isEnabled())));
But this fires NoMatchingViewException with message
NoMatchingViewException: No views in hierarchy found matching: (with
text: is "Cancel Order" and with id:
com.equinix.ecp.betatest:id/cancel_order)
So I tried changing it to
onView(allOf(withText("Cancel Order"))).check(matches(not(isEnabled())));
Somehow this matched the view but it was not a MenuItem but the TextView inside the MenuItem & since I am setting setEnabled() to MenuItem, check() Assertion won't work as expected since it is a TextView.
So my question is how to write Test for checking enabled/disabled state of MenuItem.
it would be a good idea to make use of uiautomatorviewer, to put a breakpoint in at the point your test fails, and then inspect your app's layout for clues
it sounds to me that you have two views. One with the id of R.id.cancel_order and another with text "Cancel Order" which probably has another id (or could/should).
So together they return NoMatchingView, because they aren't the same view.
They could be sibling views, or possibly one is a descendent of another. This is where uiautomatorviewer is very handy for figuring out what's happening on screen
as long as you've installed "Android SDK Platform-Tools" and "Android SDK Tools"
from Terminal:
cd /Users/<user name>/Library/Android/sdk/tools/bin
./uiautomatorviewer
(it's also helpful to save this as a script and just use an alias shortcut for convenience)
as for your matcher, i would try :
onView(allOf(
withId(R.id.cancel_order),
hasSibling(withText("Cancel Order"))
)).check(matches(not(isEnabled())));
or change hasSibling(_) to hasDescendent(_) or isDescendentOfA(_), depending on their relationship (which you can find out by using uiautomatorviewer)
I would suggest you use the IDs of the menu items to perform your checks.
I tried it with this menu:
<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="at.hellobank.hellomarkets.symbols.DetailActivity">
<item
android:id="#+id/action_1"
android:icon="#android:drawable/arrow_down_float"
android:title="Menu1"
app:showAsAction="always" />
<item
android:id="#+id/action_2"
android:enabled="false"
android:icon="#android:drawable/arrow_down_float"
android:title="Menu2"
app:showAsAction="always" />
</menu>
So one menu item is enabled one is disabled. My test to check this looks like this and is working as expected:
#Test
public void testMenuItemsStatus() throws Exception {
onView(withId(R.id.action_1)).check(matches(isEnabled()));
onView(withId(R.id.action_2)).check(matches(not(isEnabled())));
}
Generally using IDs in tests is better imho because you are more independent of typos and general language. withText("Cancel Order") probably will not work if you test the app localized in another language.
I'm using AppCompat and trying to recall the ImageView for the up/back button belonging to the toolbar.
I know R.android.id.home exists, because I can manage its click as a Menu item:
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
//this works
}
return super.onOptionsItemSelected(item);
}
Apart from that, whenever I try to call findViewById(android.R.id.home) - be it onCreate, be it onClick of a custom button - I get null.
I even get null if, in the sample above, I call findViewById(item.getItemId()).
Why is it?
This question has been asked before here, most times regarding ActionBarSherlock (which I am not using). Another time it was suggested to use:
getWindow().getDecorView().findViewById(android.R.id.home)
But it isn't working. In that question the OP also says findViewById(android.R.id.home) works on API>3.0, but that's not true for me. Any ideas?
Whether or not the "home" icon is a widget, and what class of widget it is, and what its ID is (if any), is up to the implementation of the action bar. The native action bar may do this differently for different API levels, and all of that may be different than the way appcompat-v7 does it. Let alone ActionBarSherlock or other action bar implementations.
Specifically, android.R.id.home is a menu ID, which is why you can use it in places like onOptionsItemSelected(). It is not necessarily a widget ID, which is why it may or may not work with findViewById().
Ideally, you do not attempt to mess with the internal implementation of a UI that you did not construct yourself.
do one really has to make his own Up button to style it?
I do not know, as I have never tried to style it.
As CommonsWare said android.R.id.home is a menu ID, not a widget ID. But if you want to access this home button you could do it. For example I needed it to highlight home button in in-app tutorial:
fun AppCompatActivity.getToolbarHomeIcon(): View? =
this.findViewById<Toolbar?>(R.id.toolbar)?.let { toolbar ->
val contentDescription: CharSequence = toolbar.navigationContentDescription.let {
if (it.isNullOrEmpty()) {
this.getString(R.string.abc_action_bar_up_description)
} else {
it
}
}
// Here home button should be created even if it doesn't exist before
toolbar.navigationContentDescription = contentDescription
ArrayList<View>().let { potentialViews ->
toolbar.findViewsWithText(
potentialViews,
contentDescription,
View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
)
potentialViews.getOrNull(0)
}
}
Beginner here, targetting sdk v14 and v17 for my learning...no need for older support.
I am using the master/detail template and trying to get an action menu (for SEARCH) to show up both in phone and tablet view. Actually I can get it to work, but I have to duplicate up my code in both ItemDetailActivity.java and ItemListActivity.java
These are the methods that I have to have in both for SEARCH to work:
public boolean onQueryTextChange(String newText) {
public boolean onQueryTextSubmit (String query) {
public boolean onClose () {
I only want to search the "detail", not the "list".
So my question: is there a way to associate the action bar with only the list fragment? That way I can keep the search functions in 1 file.
Thanks!
I'll go ahead and answer what I (think) I know as I don't want to leave this question open.
From tracing in the debugger, it looks to me like the phone activity and the tablet activity are separate and if you want to hook up an actionmenu, you have to hook it up to both separately.
I realise this is a specific problem however I feel like others who are dealing with compatibility must have dealt with this.
As the user swipes through various fragments in the ViewPager I would also like the actionBar menu items to change. I am not sure if this is easily doable for the compatibility actionbar, any direction or help would be extremely appreciated. I am changing the title simply via setTitle() since the ActionBarHelper handles this however i cant find anything for updating the menu items. I tried the following but it fails..
public void setMenuDynamically(int resId){
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(resId, menu);
}
Looking through the code it seems there should be an easy/obvious way to get a handle to the SimpleMenu and add an item and set its icon.
Thanks in advance ( I am hoping the google boys are reading this as the android developers Google+ suggests)
Ok well please let me know if I suck as describing things or if there just is not much knowledge on this topic. If the first I am really sorry guys. Regardless I seemed to get this to work but am unsure if this is the correct way.
AcitonBarHelper
public void updateMenu(MenuItem item) {
}
ActionBarHelperBase (for 2.2 - 3.0 devices )
#Override
public void updateMenu(MenuItem item){
addActionItemCompatFromMenuItem(item);
}
And create similar methods for honeycomb and ICS
finally i have a listener for page changing and in that listener i call...
public void setMenuDynamically(int resId, String title){
MenuItem item = menu.add(title);
item.setIcon(resId);
getActionBarHelper().updateMenu(item);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
}
I am not sure if the MenuItemCompat is necessary but I included it nonetheless. Everything seems to work great for 2.2 at least. I will most likely have to make changes in the Overrides but I can handle myself from here.
In the book The Busy Coders Guide to Android Development from CommonsWare, there is a chapter explaining how to work with the context menus.
In one example in that chapter, the context menu offers option for removing item from a list view that is generated from an ArrayList<String> object named words.
In the example the onContextItemSelected method is implemented like this:
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
ArrayAdapter<String> adapter=(ArrayAdapter<String>)getListAdapter();
switch (item.getItemId()) {
case R.id.remove:
adapter.remove(words.get(info.position));
return true;
default:
...
}
The line where adapter.remove(...) is called seems strange to me because of following fact:
Let's say the words object contains following items (in that order)
alfa
beta
gama
alfa
Now, when user loads the context menu upon the 2nd alfa and selects the option for removing it, the mentioned line actually removes the 1st alfa. And that seems wrong to me.
Instead, I would do something like this:
...
words.remove(info.position);
adapter.notifyDataSetChanged();
...
I'm not that good in Java and Android programming, so I would like to hear your opinion to this, because I want to be sure I understand well how adapters should be used.
Your idea sounds good.
The example is defunct if it behaves like you have described and you should tell Mark about it so he can check it as well (he'll probably do it anyway since he is very active on this site).