We left off in our 31 Days of Android series with implementing a base activity that our other activities extend and adding an options menu. At this point we also know how to load different activities within our application using Intents. One important topic we haven’t covered, is how to pass data between our activities. Today we’ll look at the pros and cons of several different ways of doing just that.
Static Public Objects in Activities
The first approach we’re going to look at is adding public static objects to our Activities. Open up your DaySevenActivity class and add a new static variable like so:
1: public class DaySevenActivity extends BaseActivity {
2: private TextView lblTextViewOne;
3: private EditText editText1;
4:
5: public static String MyStaticString;
Now we can access DaySevenActivity.MyStaticString anywhere. Here I will set the variable to whatever is in our EditText when the user taps our second button:
1: Button button2 = (Button) findViewById(R.id.button2);
2: button2.setOnClickListener(new OnClickListener() {
3: public void onClick(View v) {
4: startActivity(new Intent(getApplicationContext(),
5: ActivityTwo.class));
6: //Sets the Static String on the DaySevenActvity
7: MyStaticString = editText1.getText().toString();
8: }
9: });
And now I can check if MyStaticString is empty, and if it isn’t, using it to set the text property of a TextView in our second activity:
1: public class ActivityTwo extends BaseActivity {
2: @Override
3: protected void onCreate(Bundle savedInstanceState) {
4: // TODO Auto-generated method stub
5: super.onCreate(savedInstanceState);
6: setContentView(R.layout.second_layout);
7:
8: TextView textView1 = (TextView) findViewById(R.id.textView1);
9: if (!DaySevenActivity.MyStaticString.equals(""))
10: textView1.setText(DaySevenActivity.MyStaticString);
11: else
12: textView1.setText("MyStaticString is empty");
13: }
14: }
Now this is pretty poor object oriented design. Anything has access to change the MyStaticString object. We could make a private static variable with a public static accessor method, but we’re still not doing things the right way. ActivityTwo has no way of knowing that the MyStaticString has been set or that DaySevenActivity was used before. So while possible, this isn’t the best way to share data between activities.
Extending the Application Class like a Singleton
The next technique we’ll look at is extending the android.app.Application class for your project and treating it like a singleton. In order to do this, you expand your src folder, right click on your package (com.dayseven in this case) and go to New -> Class. Name your new class MyApplication and select android.app.Application for it’s Superclass. For today’s purpose, we’ll just add a String to our class along with getters and setters:
1: public class MyApplication extends Application {
2: private String myApplicationString;
3:
4: public String getMyApplicationString() {
5: return myApplicationString;
6: }
7:
8: public void setMyApplicationString(String myApplicationString) {
9: this.myApplicationString = myApplicationString;
10: }
11: }
Now when the user taps the second button, you can get the application context and cast it back to MyApplication and access our new methods:
1: Button button2 = (Button) findViewById(R.id.button2);
2: button2.setOnClickListener(new OnClickListener() {
3: public void onClick(View v) {
4: startActivity(new Intent(getApplicationContext(),
5: ActivityTwo.class));
6: //Sets the Static String on the DaySevenActvity
7: MyStaticString = editText1.getText().toString();
8: //Get our Application Instance
9: MyApplication myApp = (MyApplication) getApplication();
10: //Set the app string on our app instance
11: myApp.setMyApplicationString(editText1.getText().toString());
12: }
13: });
Now in the onCreate method of ActivityTwo you can get access in a similar manner:
1: //Get our Application Instance
2: MyApplication myApp = (MyApplication) getApplication();
3: //Set the textview's text using the app's string
4: textView1.setText(myApp.getMyApplicationString());
Lastly, in order to run this and make it work, you have to tell Android that you’re overriding the Application class. This is specified in your AndroidManifest.xml file. Open that up and add a new attribute to the application node specifying the name of your application class:
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="MyApplication">
Now when you run your application, it will use your new MyApplication class. It’s important to note that this isn’t strictly a singleton object as you could always create more instances of MyApplication instead of casting the Application Context into your MyApp class. The Android OS will only create one instance for the lifetime of your project though. In other words Android won’t screw up your singleton application class, but you could. Also important is that if we were just storing a single String and passing between two activities in a bigger application, storing the String in an application class like this might be a little bit overkill. However, if down the road we had a User object that stored a lot of information on the current application user and we used that object in all of our activities, using this singleton application class technique makes sense.
Adding Extras to New Activity Intents
The most standard and “correct” way of sharing data between activities is to pass that data into the new activity via the intent that is fired to open that activity. Let’s look at how we’re currently starting our ActivityTwo when the user taps the second button on DaySevenActivity:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(),
ActivityTwo.class));
...
}
});
If we wanted to pass a String into the second activity via the intent we need to change how we’re doing this a little bit. Since we’re currently instantiating the new Intent in our call to startActivity, we don’t have the opportunity to add any information to it. To start, you should change the code to the following:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(getApplicationContext(), ActivityTwo.class);
startActivity(myIntent);
...
}
});
Now prior to calling startActivity you have access to the myIntent object. If you look at the available methods on the Intent class, you’ll see one method, putExtra, that has been overridden to accept a plethora of different second parameter types. This allows you to attach extended data to the intent. So if you want to add your string data to the intent you could do that like so:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(getApplicationContext(), ActivityTwo.class);
myIntent.putExtra("MyString", editText1.getText().toString());
startActivity(myIntent);
...
}
});
Now in the onCreate method of ActivityTwo, you can get the intent that started the activity and pull out the String extra:
Intent myIntent = getIntent();
textView1.setText(myIntent.getStringExtra("MyString"));
If you recall, we had set up our application to fire ActivityTwo from the options menu as well. If you now go into the options menu to fire the second activity, it will load fine but there won’t be any text. The reason for that is the intent that starts ActivityTwo from the options menu never adds a String extra named “MyString”. This doesn’t cause the application to crash, it just returns null from getStringExtra(“MyString”) which means your TextView’s text property is set to nothing, hence the empty screen. One important thing to note is that you probably don’t want to use “MyString”in the code like this but may want to define a public static final String somewhere that can be shared by both your activities.
You can use putExtra and getTYPEExtra for all manner of simple variable types (Strings, ints, boolean, byte, double, etc). However, what if you want to pass a complex type via an intent? Android handles this by using Parcelable. Parcelable is an interface that can be thought of as similar to serializable. However, Parcelable has been optimized for Android which is why it is used instead of the standard java serialization. To see this in action, add a new class to your project named “PassableObject” and use the new class wizard to set the class to implement Parcelable. You should end up with a class like this:
public class PassableObject implements Parcelable {
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel arg0, int arg1) {
// TODO Auto-generated method stub
}
}
Now you’ll add a String to your class and implement the necessary methods to handle the parcel. Specifically, you need to make a constructor that will generate our object with a parcel as the input parameter and you’ll need to write what needs to be saved in the parcel. Once done your PassableObject should look like this:
public class PassableObject implements Parcelable {
private String myStringValue;
public PassableObject() {}
public PassableObject(Parcel inParcel) {
myStringValue = inParcel.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel outParcel, int flags) {
outParcel.writeString(myStringValue);
}
public String getMyStringValue() {
return myStringValue;
}
public void setMyStringValue(String myStringValue) {
this.myStringValue = myStringValue;
}
public static final Parcelable.Creator<PassableObject> CREATOR
= new Parcelable.Creator<PassableObject>() {
public PassableObject createFromParcel(Parcel in) {
return new PassableObject(in);
}
public PassableObject[] newArray(int size) {
return new PassableObject[size];
}
};
}
One important thing to point out is the CREATOR seen at the bottom. This is needed by any class that is going to implement Parcelable and is pretty boiler plate. Essentially it tells Android how to handle creating new instances of our object. Now you can create an instance of PassableObject when the user taps the button and put it in as an extra on the intent like so:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(getApplicationContext(), ActivityTwo.class);
myIntent.putExtra("MyString", editText1.getText().toString());
PassableObject passableObject = new PassableObject();
passableObject.setMyStringValue(editText1.getText().toString());
myIntent.putExtra("MyPassableObject", passableObject);
startActivity(myIntent);
...
Now in your onCreate method in ActivityTwo you can get the parcelable object and cast it back to your PassableObject class:
PassableObject passableObject = (PassableObject)
myIntent.getParcelableExtra("MyPassableObject");
textView1.setText(passableObject.getMyStringValue());
So now we have a way to pass simple variable types and complex objects to new activities using Intents. This is going come in handy even more when we start using Intents to talk to other applications.
Other Methods
There are certainly other methods that could be explored such as writing data to files in one activity and reading those files in other activities. Outside of special circumstances, most of these methods won’t deliver optimum results. Unless you find yourself in doing some very tricky stuff, your best bet will be to use the putExtra and getExtra methods to pass data between activities.
You can download the source from today here.