In an android app an activity is created and then destroyed, for example when the user open another activity or when the user presses the back button, later the user opens again the same activity that is is recreated using the data saved before the earlier destruction of the activity.
The data are saved in an object Bundle in the onSaveInstanceState method and restored in the onRestoreInstanceState method (see Recreating an Activity).
The Bundle class provides several setter and getter methods to save and restore data such as getInt and setInt for the integers and similar methods for other types of primitives, strings and one dimensional array.
In this post I write some examples to save and restore more complex objects such as the following CustomObject:
package eu.lucazanini.restoreobject.customobject; public class CustomObject { protected String name; public CustomObject() { } public CustomObject(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
The activity that saves and retrieves objects of this type is:
package eu.lucazanini.restoreobject; import eu.lucazanini.restoreobject.customobject.CustomObject; import eu.lucazanini.restoreobject.customobject.CustomParcelableObject; import eu.lucazanini.restoreobject.customobject.CustomParcelableObjectWrapper; import eu.lucazanini.restoreobject.customobject.CustomSerializableObject; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class MainActivity extends Activity { public final static String PARCELABLE_2D_ARRAY_KEY = "parcelable_2d_array_key"; public final static String PARCELABLE_ARRAY_KEY = "parcelable_array_key"; public final static String PARCELABLE_KEY = "parcelable_key"; public final static String PARCELABLE_NAME = "parcelable object"; public final static String RESTORED_LOG = "restored "; public final static String SAVED_LOG = "saved "; public final static String SERIALIZABLE_2D_ARRAY_KEY = "serializable_2d_array_key"; public final static String SERIALIZABLE_ARRAY_KEY = "serializable_array_key"; public final static String SERIALIZABLE_KEY = "serializable_key"; public final static String SERIALIZABLE_NAME = "serializable object"; public final static String TAG = MainActivity.class.getName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.d(TAG, "onRestoreInstanceState"); // section 1: parcelable object CustomParcelableObject parcelableObject; parcelableObject = (CustomParcelableObject) savedInstanceState .getParcelable(PARCELABLE_KEY); Log.d(TAG, RESTORED_LOG + parcelableObject.getName()); // section 2: serializable object CustomSerializableObject serializableObject; serializableObject = (CustomSerializableObject) savedInstanceState .getSerializable(SERIALIZABLE_KEY); Log.d(TAG, RESTORED_LOG + serializableObject.getName()); // section 3: parcelable object array CustomParcelableObject[] parcelableObjectArray; parcelableObjectArray = (CustomParcelableObject[]) savedInstanceState .getParcelableArray(PARCELABLE_ARRAY_KEY); for (int i = 0; i < 3; i++) { Log.d(TAG, RESTORED_LOG + parcelableObjectArray[i].getName()); } // section 4: serializable object array CustomSerializableObject[] serializableObjectArray; serializableObjectArray = (CustomSerializableObject[]) savedInstanceState .getSerializable(SERIALIZABLE_ARRAY_KEY); for (int i = 0; i < 3; i++) { Log.d(TAG, RESTORED_LOG + serializableObjectArray[i].getName()); } // section 5: parcelable object 2d array CustomParcelableObjectWrapper parcelableObject2DArray; parcelableObject2DArray = (CustomParcelableObjectWrapper) savedInstanceState .getParcelable(PARCELABLE_2D_ARRAY_KEY); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { Log.d(TAG, RESTORED_LOG + parcelableObject2DArray .getCustomParcelableObject(i, j) .getName()); } } // section 6: serializable object 2d array CustomSerializableObject[][] serializableObject2DArray; serializableObject2DArray = (CustomSerializableObject[][]) savedInstanceState .getSerializable(SERIALIZABLE_2D_ARRAY_KEY); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { Log.d(TAG, RESTORED_LOG + serializableObject2DArray[i][j].getName()); } } // section 7: object in application MyApplication myApplication = (MyApplication) getApplication(); CustomObject[][] customObjectArray = myApplication .getCustomObjectArray(); Log.d(TAG, myApplication.getObjectDescription(RESTORED_LOG)); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.d(TAG, "onSaveInstanceState"); // section 1: parcelable object CustomParcelableObject parcelableObject = new CustomParcelableObject( PARCELABLE_NAME); outState.putParcelable(PARCELABLE_KEY, parcelableObject); Log.d(TAG, SAVED_LOG + parcelableObject.getName()); // section 2: serializable object CustomSerializableObject serializableObject = new CustomSerializableObject( SERIALIZABLE_NAME); outState.putSerializable(SERIALIZABLE_KEY, serializableObject); Log.d(TAG, SAVED_LOG + serializableObject.getName()); // section 3: parcelable object array CustomParcelableObject[] parcelableObjectArray = new CustomParcelableObject[3]; for (int i = 0; i < 3; i++) { parcelableObjectArray[i] = new CustomParcelableObject( PARCELABLE_NAME + " at " + i); } outState.putParcelableArray(PARCELABLE_ARRAY_KEY, parcelableObjectArray); for (int i = 0; i < 3; i++) { Log.d(TAG, SAVED_LOG + parcelableObjectArray[i].getName()); } // section 4: serializable object array CustomSerializableObject[] serializableObjectArray = new CustomSerializableObject[3]; for (int i = 0; i < 3; i++) { serializableObjectArray[i] = new CustomSerializableObject( SERIALIZABLE_NAME + " at " + i); } outState.putSerializable(SERIALIZABLE_ARRAY_KEY, serializableObjectArray); for (int i = 0; i < 3; i++) { Log.d(TAG, SAVED_LOG + serializableObjectArray[i].getName()); } // section 5: parcelable object 2d array CustomParcelableObjectWrapper parcelableObject2DArray = new CustomParcelableObjectWrapper( PARCELABLE_NAME); outState.putParcelable(PARCELABLE_2D_ARRAY_KEY, parcelableObject2DArray); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { Log.d(TAG, SAVED_LOG + parcelableObject2DArray .getCustomParcelableObject(i, j) .getName()); } } // section 6: serializable object 2d array CustomSerializableObject[][] serializableObject2DArray = new CustomSerializableObject[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { serializableObject2DArray[i][j] = new CustomSerializableObject( SERIALIZABLE_NAME + " at " + i + ", " + j); } } outState.putSerializable(SERIALIZABLE_2D_ARRAY_KEY, serializableObject2DArray); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { Log.d(TAG, SAVED_LOG + serializableObject2DArray[i][j].getName()); } } // section 7: object in application MyApplication myApplication = (MyApplication) getApplication(); myApplication.setCustomObjectArray(); Log.d(TAG, myApplication.getObjectDescription(SAVED_LOG)); } }
The methods onSaveInstanceState and onRestoreInstanceState are divided into sections where the object CustomObject or an array of one or more dimensions of this object is saved and then restored.
All sections except the last one use one of the following interfaces:
- Serializable: “maker interface” with no methods to implement, available in Java
- Parcelable: an interface, available in Android SDK
Between the two techniques Parcelable is the most efficient, in the net you find a lot of documentation about, as here.
-
- section 1: here I use the methods putParcelable and getParcelable for the classes extending the Parcelable interface; in this example the class CustomParcelableObject extends CustomObject and implements Parcelable
package eu.lucazanini.restoreobject.customobject; import android.os.Parcel; import android.os.Parcelable; public class CustomParcelableObject extends CustomObject implements Parcelable { public static final Parcelable.Creator<CustomParcelableObject> CREATOR = new Parcelable.Creator<CustomParcelableObject>() { @Override public CustomParcelableObject createFromParcel(Parcel in) { return new CustomParcelableObject(in); } @Override public CustomParcelableObject[] newArray(int size) { return new CustomParcelableObject[size]; } }; public CustomParcelableObject(String name) { super(name); } private CustomParcelableObject(Parcel in) { name = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); } }
- section 2: here I use the methods putSerializable and getSerializable for the classes extending the Serializable interface; in this example the class CustomSerializableObject extends CustomObject and implements Serializable
package eu.lucazanini.restoreobject.customobject; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class CustomSerializableObject extends CustomObject implements Serializable { private static final long serialVersionUID = -1669600905661049718L; public CustomSerializableObject(String name) { super(name); } private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException { inputStream.defaultReadObject(); } private void writeObject(ObjectOutputStream outputStream) throws IOException { outputStream.defaultWriteObject(); } }
- section 3: here the restored object is an one dimensional array of objects CustomParcelableObject extending Parcelable using the methods putParcelableArray and getParcelableArray
- section 4: here the restored object is an one dimensional array of objects CustomSerializableObject extending Serializable using the methods putSerializable and getSerializable; remember that arrays are objects that always extend the Serializable interface
- section 5: here the restored object is an array of two dimensional objects (CustomParcelableObject[][] array), field of a wrapper class that extends Parcelable:
package eu.lucazanini.restoreobject.customobject; import android.os.Parcel; import android.os.Parcelable; public class CustomParcelableObjectWrapper implements Parcelable { public static final Parcelable.Creator<CustomParcelableObjectWrapper> CREATOR = new Parcelable.Creator<CustomParcelableObjectWrapper>() { @Override public CustomParcelableObjectWrapper createFromParcel(Parcel in) { return new CustomParcelableObjectWrapper(in); } @Override public CustomParcelableObjectWrapper[] newArray(int size) { return new CustomParcelableObjectWrapper[size]; } }; private CustomParcelableObject[][] array; public CustomParcelableObjectWrapper(String name) { array = new CustomParcelableObject[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { array[i][j] = new CustomParcelableObject(name + " at " + i + ", " + j); } } } private CustomParcelableObjectWrapper(Parcel in) { array = (CustomParcelableObject[][]) in .readArray(CustomParcelableObjectWrapper.class.getClassLoader()); } @Override public int describeContents() { return 0; } public CustomParcelableObject getCustomParcelableObject(int i, int j) { return array[i][j]; } public void setCustomParcelableObject(int i, int j, String name) { array[i][j].setName(name); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeArray(array); } }
- section 6: here the restored object is a two dimensional array of CustomSerializableObject
- section 7: here the CustomObject is not saved and then restored as in the above sections but it is preserved in the class MyApplication that extends Application:
package eu.lucazanini.restoreobject; import eu.lucazanini.restoreobject.customobject.CustomObject; import android.app.Application; public class MyApplication extends Application { public final static String OBJECT_IN_APPLICATION = "application object"; public final static String TAG = MyApplication.class.getName(); private CustomObject[][] customObject2DArray; public CustomObject[][] getCustomObjectArray() { return customObject2DArray; } public String getObjectDescription(String prefix) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { sb.append(prefix + customObject2DArray[i][j].getName()); sb.append("\n"); } } return sb.toString(); } public void setCustomObjectArray() { customObject2DArray = new CustomObject[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { customObject2DArray[i][j] = new CustomObject( OBJECT_IN_APPLICATION + " at " + i + ", " + j); } } } }
this method can be used when the screen rotates and the activity is destroyed but not the Application object specified in the AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.lucazanini.restoreobject" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:name="eu.lucazanini.restoreobject.MyApplication" > <activity android:name="MainActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
as you can see in Google developers the Application class can be used in order to “maintain global application state” but in the net you can find some issues as here.
- section 1: here I use the methods putParcelable and getParcelable for the classes extending the Parcelable interface; in this example the class CustomParcelableObject extends CustomObject and implements Parcelable
You can download the app here, to test you need rotate the device or emulator (Ctrl F11) for example.
Leave a Reply