Restoring complex objects at restart of an activity

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.

    1. 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);
          }
      
      }
      
    2. 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();
          }
      
      }
      
    3. section 3: here the restored object is an one dimensional array of objects CustomParcelableObject extending Parcelable using the methods putParcelableArray and getParcelableArray
    4. 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
    5. 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);
          }
      
      }
      
    6. section 6: here the restored object is a two dimensional array of CustomSerializableObject
    7. 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.

You can download the app here, to test you need rotate the device or emulator (Ctrl F11) for example.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.