I am using the same tabBar across multiple Activities. And since there are extensive logic involved for onOptionsItemSelected, I want to write the relevant methods once and then reuse them. Hence I am deciding to created a super class called CustomActionBarActivity and then have the children activities extend it. One particular problem I need help with is how can I tell which child has caused the onOptionsItemSelected to be called? To elucidate, I will present the general code and then a failed attempt at a solution.
Here is the general code
public class CustomActionBarActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.tab_dog:
startActivity(new Intent(this, DogActivity.class));
return true;
case R.id.tab_cat:
startActivity(new Intent(this, CatActivity.class));
return true;
case R.id.tab_mouse:
startActivity(new Intent(this, MouseActivity.class));
return true;
case R.id.tab_goose:
startActivity(new Intent(this, GooseActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Failed attempt
If I try, for instance,
case R.id.tab_dog:
if(!(this instanceof DogActivity))
startActivity(new Intent(this, DogActivity.class));
then I get a compile error such that CustomActionBarActivity is not compatible with DogActivity. Thanks for any help you can provide.
Instead of having your parent class inspect the children using reflection (which is pretty fragile since it doesn't scale with the number of children subclasses you create), maybe you could take advantage of dynamic dispatch instead.
For example, maybe you could declare an abstract method in your parent activity like:
protected abstract void onTabItemSelected(MenuItem item);
Then your children activities can override this method depending on the desired behavior. For example, DogActivity might implement it like this:
protected boolean onTabItemSelected(MenuItem item) {
if (item.getItemId() != R.id.dog_tab) {
startActivity(new Intent(this, DogActivity.class));
return true;
}
return false;
}
The onOptionsItemSelected method would then be implemented like this:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (onTabItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
Let me know if I misunderstood the question. Either way, you might be able to modify this approach to suit your use case.
Related
After defining FLAG_ACTIVITY_NO_ANIMATION for the back arrow in the action bar to correct to animation when the toolbar back arrow is clicked, a warning is then returned. What is the best way to get rid of this warning?
Method invocation 'addFlags' may produce 'java.lang.NullPointerException'
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
final Intent intent = getParentActivityIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
Wrap it into an if intent != null.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
final Intent intent = getParentActivityIntent();
if(intent != null){
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
}else{
//Do some error handling.
}
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
From the doc of the getParentActivityIntent:
#return: a new Intent targeting the defined parent of this activity or null if
there is no valid parent.
This method returns null only if there is no parent Activity and so they marked the #Nullable annotation for the return value. That's why you are getting the warning.
If you are sure that you have defined the parent Activity in the manifest, you don't have to worry about the NullPointerException, you can suppress this warning.
To suppress, you have to add #SuppressWarnings("ConstantConditions") to the method.
And it is a good practice to add a comment above the annotation explaining why you have suppressed the warning.
Below is the code for my submenu buttons and I'm trying to make it delete the note and return to the main list view. The delete option is called "Red" for now.
I copied my delete code from my main activity thinking it would work, but it does not. I'm very new to android coding, so help would be appreciated.
This is how I delete in my Main Activity.java
#Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
currentNoteId = (int)info.id;
menu.add(0, MENU_DELETE_ID, 0, "Delete");
}
#Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == MENU_DELETE_ID) {
Noteitem note = notesList.get(currentNoteId);
datasource.remove(note);
refreshDisplay();
}
return super.onContextItemSelected(item);
}
Here is my code for my NoteEditorActivity.java
Again I'm trying to delete, but I can't seem to figure out how to delete the note from the submenu.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_exit:
EditText et = (EditText)findViewById(R.id.noteText);
if (et.length() > 0) {
saveAndFinish();
}
else
{
finish();
}
case R.id.menu_red:
currentNoteId = (int) MENU_DELETE_ID;
datasource.remove(note);
return true;
default:
return super.onOptionsItemSelected(item);
}
Put break statements in your switch case
switch (item.getItemId())
{
case R.id.action_exit:
EditText et = (EditText)findViewById(R.id.noteText);
if (et.length() > 0){
saveAndFinish();
}else{
finish();
}
//you are missing this!!!
break;
case R.id.menu_red:
datasource.remove(note);
finish();
break;
default:
return super.onOptionsItemSelected(item);
break;
}
Try reading this here also: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
its just weird..you aren't calling notesList.add() method anywhere in your code, so I just think it's empty at all..
you are certainly missing the break statement there, but I guess it is not the issue why your note is not deleted after clicking in the menu item. You are saving your note in the "previous" (in terms of backstack) activity? if so, you might try to just alter the return code for the setActivityResult() call (or add some extras to the intent) and then check for it in your onActivityResult() callback..
because right now everytime you close the activity via back key, the notes is saved (the saveAndFinish() method gets called);
please describe better where you actually do save notes (to DB or so) and where you wanna delete them..I could then provide you with some code snippet probably.
I'm new in Java (Eclipse), and I want to know if there is any way to create a file with some piece of code and just call it in the class.
Because I have a lot of different classes for different results but there is some code that don't change from one to another (the menu for example) and I want to make this more practical when I have to adapt that code, without change class by class.
Is there any way to do this ?
I accomplish this in my projects with classes I call Helper classes. I got the idea from the GoogleIO app source code. They have a class called the ActivityHelper. It does exactly what your are talking about, puts code that is used over and over in one place. Here is my MenuHelper class as an example. My menu is the same for a lot of my Activities so it made sense to do it this way...
public class MenuHelper {
public static void build(Menu menu) {
menu.add(Menu.NONE, Key.Activity.LOGOUT, 0, R.string.label_logout)
.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
menu.add(Menu.NONE, Key.Activity.FEEDBACK, 0,
com.bytebenderapps.dbitly.R.string.label_feedback).setIcon(
android.R.drawable.ic_menu_send);
}
public static boolean onMenuItemSelected(int featureId, MenuItem item,
Context context) {
switch (item.getItemId()) {
case Key.Activity.LOGOUT:
removeCredentials(context);
startLoginActiviy(context);
break;
case Key.Activity.FEEDBACK:
startFeedbackActivity(context);
break;
}
return true;
}
private static void startLoginActiviy(Context context) {
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
private static void removeCredentials(Context context) {
Editor editor = PreferenceManager.getDefaultSharedPreferences(context)
.edit();
editor.clear();
editor.commit();
}
public static void toggleLogoff(Menu menu, boolean userLoggedIn) {
MenuItem item = menu.findItem(Key.Activity.LOGOUT);
item.setEnabled(userLoggedIn);
}
private static void startFeedbackActivity(Context context) {
context.startActivity(new Intent(context, FeedbackActivity.class));
}
}
And then the implementation in one of my activities
public class MyActivity {
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuHelper.build(menu);
return true;
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
super.onMenuItemSelected(featureId, item);
return MenuHelper.onMenuItemSelected(featureId, item, this);
}
}
It will help us solve your doubts if you post some piece of code or give us a better clue what you want to achieve and what is your current approach.
General answer would be that you can and should separate your code in different classes, but from case to case there are different approaches to apply
anyhow this post/thread can be useful to you.
I am trying to implement some code on every activity and don't want to copy and page the code into each activity.
Originally I just had a parent activity with the code then extended all of the others but I couldn't do this on ListActivities or ExpandableListActivities.
I think this will be done by using an interface class then having each activity implement this. However when I try to do this Eclipse gives me an error and says to remove the method body.
Here is what I have so far
import android.content.Intent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
public interface MenuOptions {
/**
* Method called when the hardware menu button is called. Uses optionmenu.xml for layout
*/
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.optionmenu, menu);
return true;
}
/**
* Event listener for the options menu. If home is pressed user is sent to home screen. If settings is pressed user is sent to setting screen
* User is passed as an extra
*/
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent nextIntent = null;
switch (item.getItemId()) {
case R.id.home:
Toast.makeText(this, "You pressed the icon!", Toast.LENGTH_LONG).show();
nextIntent = new Intent(this, Home.class);
break;
case R.id.settings:
Toast.makeText(this, "You pressed the text!", Toast.LENGTH_LONG).show();
nextIntent = new Intent(this, Settings.class);
break;
}
nextIntent.putExtra("user", user);
startActivity(nextIntent);
return true;
}
}
Interface classes in Java should only contain method signatures and no implementation. Therefore you have to create a base class:
public class MenuOptions extends Activity {
/**
* Method called when the hardware menu button is called. Uses optionmenu.xml for layout
*/
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.optionmenu, menu);
return true;
}
/**
* Event listener for the options menu. If home is pressed user is sent to home screen. If settings is pressed user is sent to setting screen
* User is passed as an extra
*/
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent nextIntent = null;
switch (item.getItemId()) {
case R.id.home:
Toast.makeText(this, "You pressed the icon!", Toast.LENGTH_LONG).show();
nextIntent = new Intent(this, Home.class);
break;
case R.id.settings:
Toast.makeText(this, "You pressed the text!", Toast.LENGTH_LONG).show();
nextIntent = new Intent(this, Settings.class);
break;
}
nextIntent.putExtra("user", user);
startActivity(nextIntent);
return true;
}
}
And your activity:
public class YourActivity extends MenuOptions {
/*...*/
}
Interfaces are not allowed to have method bodies. This is because it describes the interface of an object, not the method itself.
The problem with the same menu options shared by all activities is a common one. The easiest way of handling this is to define 2 or 3 superclasses (for ListActivities and MapActivities).
As far as I know an interface only describes what methods must be supported by a class implementing the interface.
This is the difference between implementing an interface and extending a class.
- so you can't write an interface thats used by all your classes, only define a set of methods which must be defined by any class implementing that interface
I would say your best bet is to create a new class which can be used by all your activities to do the same job. Then you'll need an instance of that class which is created and initialised by each activity onCreate (or somewhere else)
Alternately you may find you can make your new class static so you don't need to create an instance, just call a static method at the right times
One other way of doing it is to create a subclass of Activity which includes the standard behaviour and create all your activities as a subclass of this. Where you have ListActivities (for instance) you'll have to do some recoding, making them subclassed activities containing lists
I have an Activity in a Library that has a menu in it. The menu has all the standard attributes as well as the #Override on onCreateOptionsMenu and onOptionsItemSelected.
In my actual project which imports above library I have another activity that extends teh above activity. For this specific implementation of this program I want to have a different menu on this activity so inside this activities code base add add onCreateOptionsMenu and onOptionsItemSelected with the proper #Override, but i get the menu from the Library, not the override in the library.
What could I be doing wrong?
Base Class:
public class ListItems extends ListActivity {
public static final int LOGOUT = 0;
public static final int HISTORY = 1;
public static final int REFRESH = 2;
#Override
public boolean onCreateOptionsMenu(Menu menu){
menu.add(0, LOGOUT ,0,"Log Out");
menu.add(0,HISTORY,0,"Order History");
menu.add(0,REFRESH,0,"SMS");
return true;
}
#Override
public boolean onOptionsItemSelected (MenuItem item){
TextView textView = (TextView)findViewById(com.imobileminutes.library.R.id.text);
switch (item.getItemId()){
case LOGOUT:
Intent logOut = new Intent(ListItems.this,MainScreen.class);
startActivity(logOut);
finish();
return true;
case HISTORY:
Intent orderHistory = new Intent(this,OrderItems.class);
startActivity(orderHistory);
return true;
case REFRESH:
Intent orderHistory = new Intent(this,OrderItems.class);
startActivity(orderHistory);
return true;
}
return false;
}
}
Override Class
public class ListItems extends com.imobileminutes.library.ListItems {
static final int SMS = 2;
#Override
public boolean onCreateOptionsMenu(Menu menu){
Log.d("onCreateOptionsMenu", "My onCreateOptionsMenu ran");
Toast.makeText(getApplicationContext(), "HEllo Menu Created", Toast.LENGTH_LONG).show();
super.onCreateOptionsMenu(menu);
return true;
}
#Override
public boolean onOptionsItemSelected (MenuItem item){
Log.d("onCreateOptionsMenu", "My onOptionsItemSelected ran");
Toast.makeText(getApplicationContext(), "HElloonOptionsItemSelected", Toast.LENGTH_LONG).show();
super.onOptionsItemSelected(item);
return false;
}
}
CONCLUSION
It ended up being that my library was calling ListItems.this and CreateAccount.this which was referencing the activities in the library not in the extended classes.
I added an intent for every extended activity type and nwo call that, thus bringing up the extended activity rather than the base activity.
Thanks for the help guys.
Either you didn't copy and paste the code correctly, or part of the problem is in your base class, which provides the same definition for onCreateOptionsMenu as your derived class:
#Override
public boolean onCreateOptionsMenu(Menu menu){
menu.add(0, LOGOUT ,0,"Log Out");
menu.add(0,HISTORY,0,"Order History");
menu.add(0,REFRESH,0,"SMS");
return true;
}
That last line can't be right -- I'm pretty sure the last line of the base class implementation should be: menu.add(0,REFRESH,0,"REFRESH");.
But that last line of onCreateOptionsMenu also needs to be changed in the derived class:
#Override
public boolean onCreateOptionsMenu(Menu menu){
menu.add(0, LOGOUT ,0,"Log Out");
menu.add(0, HISTORY,0,"Order History");
menu.add(0, SMS,0,"SMS"); // Use the correct constant (SMS instead of REFRESH)
return true;
}
EDIT: Also, you can simplify the code a bit, by re-using the superclass implementation for those choices where you're currently just copying the superclass code (LOGOUT and HISTORY):
#Override
public boolean onOptionsItemSelected (MenuItem item){
TextView textView = (TextView)findViewById(com.imobileminutes.library.R.id.text);
// Left this as a 'switch'; you might want to use an 'if' instead.
switch (item.getItemId()){
case SMS:
AppClass.sendSMS("8135551212", "Hello Jason");
return true;
}
return super.onOptionsItemSelected(item);
}
Use the fully qualified name on the import my suspicion is you are referencing the wrong class.
Turns out it was the way I was calling the Activitys. I was calling the Activity directly in the Library rather than calling the overridden one. Since the overriden ones will be dynamic depending on the Apps naming, I ended up using Intents that will search for the callers packagename and append the specific intent onto it.
As soon as I set all that up, my SMS menu item showed.