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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
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
1234567891011121314151617181920212223242526272829303132333435363738package 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>() {@Overridepublic CustomParcelableObject createFromParcel(Parcel in) {return new CustomParcelableObject(in);}@Overridepublic CustomParcelableObject[] newArray(int size) {return new CustomParcelableObject[size];}};public CustomParcelableObject(String name) {super(name);}private CustomParcelableObject(Parcel in) {name = in.readString();}@Overridepublic int describeContents() {return 0;}@Overridepublic 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
123456789101112131415161718192021222324252627package 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 implementsSerializable {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:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package 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>() {@Overridepublic CustomParcelableObjectWrapper createFromParcel(Parcel in) {return new CustomParcelableObjectWrapper(in);}@Overridepublic 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());}@Overridepublic 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);}@Overridepublic 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:
123456789101112131415161718192021222324252627282930313233343536373839package 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
12345678910111213141516171819202122232425<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="eu.lucazanini.restoreobject"android:versionCode="1"android:versionName="1.0" ><uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="21" /><applicationandroid: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.