651.288.7000 info@intertech.com

Saving (and Retrieving) Android Instance State – Part 1

by | Jan 8, 2013

imageThis begins a two part blog posting dedicated to explaining and exemplifying how Android (activity/fragment) state can be saved and restored.  In this first part, I provide information about the use of onSaveInstanceState( ) and onRestoreInstanceState( ) methods of the activity for saving and restoring state.  In Part 2 of this series (my next post), I will take you through an explanation of a fragment’s setRetainInstance( ) and getRetainInstance( ) methods for managing state.  The setRetainInstance( ) and getRetainInstance( ) methods are replacements for the now deprecated onRetainNonConfigurationInstance( ) and getLastNonConfigurationInstance( ) activity methods.  These later two methods were deprecated as of Android 3.2.

Why do we need to save instance state?

Android activities have a lifecycle (for a diagram of this lifecycle see here).  As an activity becomes partially hidden (paused) or fully hidden (stopped), possibly even destroyed, your applications need a way to keep valuable state (i.e. data) the activity has obtained (probably from user input) or created even after the activity has gone away.

As an example, an orientation change can cause your activity to be destroyed and removed from memory and then recreated again.  Unless you avail yourself to certain state saving mechanisms, any data your activity has at the time of orientation change can be completely lost in this seemingly simple act.

How do we save activity instance state?

The Activity class provides the first, and possibly easiest, means of saving and restoring activity state.  The onSaveInstanceState( ) method is a callback method that is not shown on most Activity lifecycle diagrams.  Nonetheless, this method is called by Android between the onPause( ) and onStop( ) methods.

image

Important side note:  the API documentation explicitly states the onSaveInstanceState( ) method will be called before onStop( ) but makes no guarantees it will be called before or after onPause( ).  In the testing I did on the Android Virtual Device and two actually devices, it did get called between onPause( ) and onStop( ), but the quote below suggests one should not take this for granted.

“If called, this method will occur before onStop(). There are no guarantees about whether it will occur before or after onPause().”

The onSaveInstanceState( ) method is passed a Bundle object as a parameter.  Data you want saved for the activity should be placed in the Bundle.  In loose terms, a Bundle is a custom Android hash map.  Data you want saved for the activity (for its redisplay or recreation) are stored in the Bundle with a String key.  The key allows the data to be retrieved back from the Bundle.image

Saving State using onSaveInstanceState( ) Example

As an example, say your activity has a Calendar object property.  This property might be used to track and display the “start time” when a user opened the activity and started entering data (not a far-fetched idea given that some transactions must be completed within a certain amount of time or are discarded).

public class DataEntryActivity extends Activity {

    private Calendar startTime = Calendar.getInstance();
    // ... the rest of the activity
}

SNAGHTML20054d6c

To underscore the importance of saving an activity’s state, what would happen if the orientation of the device were to change while the user is looking at this activity?  Indeed, because the activity would be destroyed and recreated on an orientation change, a brand new instance of the activity is created and therefore a new “start time” would be created and displayed to the user on this orientation change.  Yikes!  You would want the start time to be maintained, even if the actual activity instance is not the same one that kicked off the data entry.

To keep the start time state across activity lifecycle events (to include destroy and recreation), implement the onSaveInstanceState( ) method as follows:

@Override
protected void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putSerializable("starttime", startTime);
}

The start time data is now saved under the key of “starttime” in the bundle.  Again, this method will be called automatically by Android before onStop( ) and the serialized Calendar object will be saved in the bundle which gets maintained and used across any instance of this activity.

Important side note:  the bundle is for data managed by instances of the activity class and not across activity classes.  In other words, you can not put state in the bundle of Activity A and have Activity B opened and expect to see the state of Activity A in its bundle.

How do we retrieve saved state?

If you notice, the onCreate( ) lifecycle method of the Activity class is passed a Bundle object as a parameter.  This is the same Bundle object that was used to save state data in the onSaveInstanceState( ) method as shown above.  Android passes the Bundle to each instance of an activity it creates.  So, you could programmatically reach into that Bundle object to retrieve state, like the Calendar object above, in the onCreate( ) method.  The code example below (specifically lines 8-12) does exactly that.

   1: @Override
   2: protected void onCreate(Bundle bundle) {
   3:     super.onCreate(bundle);
   4:     Log.v(TAG, "Inside of onCreate");
   5:     setContentView(R.layout.activity_data_entry);
   6:     timeTV = (TextView) findViewById(R.id.time_tv);
   7:
   8:     if ((bundle != null)
   9:             && (bundle.getSerializable("starttime") != null)) {
  10:         startTime = (Calendar) bundle
  11:                 .getSerializable("starttime");
  12:     }
  13: }

Import side note:  notice the check for the bundle being null?  Remember, the onCreate( ) method gets called each time the activity is created, to include the very first time the activity is created and before there was ever any state.  So make sure you check for the bundle being null before trying to use it and extract state data.  The bundle will be null on the first call to onCreate( ).

Alternately, use the onRestoreInstanceState( ) method to extract saved state from the Bundle and restore it into new instances of the activity.  The onRestoreInstanceState( ) method is another callback lifecycle method that is also rarely seen in the activity lifecycle diagrams.  The onRestoreInstanceState( ) method is automatically invoked by Android between the onStart( ) and the onResume( ) lifecycle methods.

image

So, in order to restore the state of your activity, simply implement the onRestoreInstanceState( ) method to have it retrieve and restore activity state.  This allows you to separate creation code from state restoring code.  The example code below restores the Calendar “start time.”

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    Log.v(TAG, "Inside of onRestoreInstanceState");
    startTime = (Calendar) savedInstanceState.getSerializable("starttime");
}

You’ll notice that the check for null isn’t necessary as Android will have created an empty Bundle object by the time the method is called even on the initial creation of the activity.

What kind of state can be saved in the Bundle?

As I indicated above, the Bundle object is a kind of simple Android hash map.  So data is stored in key value pairs.  The keys are always Strings.  The values must be of android.os.Parcelable type.  Parcelables are close to Java’s Serializable, but not the same.  In fact, the Android documentation is quick to point out that the Parcelable API “is not a general-purpose serialization mechanism.”  Without getting into all the gory details of the Parcelable type, suffice it to say that types of data that can be placed into the value side of a Bundle are listed in the table below.

Allowed Bundle Value Types
boolean
boolean[ ]
Bundle (yes – store a Bundle in a Bundle)
byte
byte[ ]
char
char[ ]
CharSequence
CharSequence[ ]
ArrayList<CharSequence>
double
double[ ]
float
float[ ]
int
int [ ]
ArrayList<Integer>
long
long[ ]
Serializable
short
short[ ]
SparseArray – a map of integer to Object and is more efficient than a HashMap.  Used more internally by Android (see below)
String
String[ ]
ArrayList<String>

Automatic View State Saving/Restoring

The Bundle, the onSaveInstanceState( ) and onRestoreInstanceState( ) methods are already used by the Activity super class to automatically save and restore the state of any View object in the Activity so long as the View component has an id.  To demonstrate, say in the data entry form used in my examples, you start to enter some data in the “Your Data” EditText entry field.  If the orientation were to change when the user has provided data in the EditText field, that data would be lost since the Activity instance and its associated view components need to be destroyed and recreated.  But as you can see below, the data is not lost!  How does this happen?

image  image

Android stores all View component data (to which EditText is a subclass) in the Bundle automatically so long as you have provided the View an id property.  The View’s id serves as the key in the Bundle to the data that needs to be restored.  In fact, adding a little bit of code to onRestoreInstanceState( ) as shown below (specifically the code of lines 4-16) allows you to see the automatically saved View state.

   1: @Override
   2: protected void onRestoreInstanceState(Bundle savedInstanceState) {
   3:     startTime = (Calendar) savedInstanceState.getSerializable("starttime");
   4:     Bundle viewHierarchy = savedInstanceState
   5:             .getBundle("android:viewHierarchyState");
   6:     if (viewHierarchy != null) {
   7:         SparseArray views = viewHierarchy.getSparseParcelableArray("android:views");
   8:         if (views != null) {
   9:             for (int i = 0; i < views.size(); i++) {
  10:                 Log.v(TAG, "key -->" + views.get(i));
  11:                 Log.v(TAG, "value --> " + views.valueAt(i));
  12:             }
  13:         }
  14:     } else {
  15:         Log.v(TAG, "no view data");
  16:     }
  17:     super.onRestoreInstanceState(savedInstanceState);
  18:     Log.v(TAG, "Inside of onRestoreInstanceState");
  19: }

Inside of the Bundle, under a key of “android:viewHierarchyState” Android puts another Bundle object.  This bundle holds, as its name implies, the View state.  Inside the view hierarchy state bundle Android stores a SparseArray under the key “android:views.”  A sparse array is an Android specific data structure that is an integer/index to Object map and is more efficient than a HashMap.  Inside of the SparseArray is the View state data stored per View component id.  Using the Eclipse debug inspector I have a snapshot of the SparseArray holding the state of my simple Activity (specifically the text data in the Your data EditText view).

SNAGHTML2879d75f  image

Again, you don’t have to do anything special to allow Android to save and restore View state so long as you provide the View components and id property.  Also, and very importantly, if you do provide your own implementation of onSaveInstanceState( ) and onRestoreInstanceState( ) methods, remember to call to the super (Activity class) implementation of these methods.  It is in the super class implementations that the work of saving and restoring View state is actually accomplished.  So,if you were to leave out the call to super.onSaveInstanceState( ) in your onSaveInstanceState( ) method, the View data would not be saved automatically!

Wrap Up

Well, that wraps up the first of two posts on Android state saving and retrieving.  Again, in the next post, look forward to how to address this with fragment’s setRetainInstance( ) and getRetainInstance( ) methods.  I’ll also compare/contrast the two approaches in that post.  If you have a desire to learn more about Android, consider signing up for Intertech’s Complete Android class.  You can find out more information about the class and sign up here.  If you have an Android application that you need developed, consider hiring Intertech’s fabulous team of consultants to do the work or provide you any assistance you need in getting your app out on millions of ‘Droid devices.

Looking for training in mobile development? Check out the mobile development courses that we have available!

Intertech also provides Mobile Development Consulting. From Android to iPhone to Windows phone, we can help you build the app that your company needs.

Follow Us

Categories

Like What You've Read?

Subscribe to the Blog.

Every Friday we send that week's content from our Developers via email. Try it out!

Some ad blockers can block the form below.

You have Successfully Subscribed!

Pin It on Pinterest

Share This