Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Preserving Activity State

Author: Daniel Fowler
Published? false
FormatLanguage: WikiFormat

Problem:

Users expect Apps to be in the same position as when they were interrupted, e.g. from taking a call. Developers need to know how to restore Activity state.

Solution:

Android has built in support for saving and restoring state, by overriding a couple of functions an Activity's state can be preserved.

Discussion:

Android developers soon become aware of the precarious existence of the Activities they code, with the Android operating system pausing, stopping or killing Activities as it sees fit (see the recipes on Android Lifecycle and Activity LifeCycle Scenarios for Testing. Despite this users expect Applications to be robust, when the user returns to a screen it is usually expected to be in the same state as when it was last seen. Try the following simple test, a single EditText in an Activity.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    <EditText android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

Run it and type a number e.g. 100. Then turn the device from portrait to landscape (or landscape to portrait). On the emulator use Ctrl-F11.

The entered number disappears. Now do the same again but this time give the EditText an id.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    <EditText android:id="@+id/edittext1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

This time the number is preserved. Switching the device from portrait to landscape causes Android to stop and restart the Activity, allowing Activities the opportunity to redraw a screen for the different dimensions. With stopping and starting an Activity a common occurrence users would be annoyed if input kept being lost. Android have a pair of functions called onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) which maintains such input, but only if that input can be identified, hence the need for the EditText to have an id. This function pair can also be overridden so that state variables not associated with user input fields can also be saved and restored.

Consider the following simple Application which can be used to sum some numbers. An EditText is used to enter a number, a plus button adds the entered number to a total which is then displayed in the EditText. The next number can be entered, plus pressed again and the running total displayed, and so on. The total is set back to zero with the C (for clear) button. Here is the layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    <EditText android:id="@+id/edittext1"
        android:layout_width="140dp"
        android:layout_height="wrap_content"
        android:textSize="25sp"/>
    <Button android:id="@+id/plus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+"
        android:width="60dp"
        android:textSize="25sp"/>
    <Button android:id="@+id/clear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="C"
        android:width="60dp"
        android:textSize="25sp"/>
</LinearLayout>

And code:

public class main extends Activity {
    float total;	//store running total
    EditText number;	//display number to add and total
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        number = (EditText) findViewById(R.id.edittext1);
        //Add
        findViewById(R.id.plus).setOnClickListener(new OnClickListener(){
            public void onClick(View arg0) {
    	        try {
    		    total += Float.parseFloat(number.getText().toString());
    		    number.setText(Float.toString(total));
    		    number.setSelection(0, number.getText().length());
    		} catch (Exception ex) {}
    	    }
        });
        //Clear
        findViewById(R.id.clear).setOnClickListener(new OnClickListener(){
            public void onClick(View arg0) {
    	        total=0f;
    		number.setText("");
    	    }
        });
    }
}

Run the program, press 2, then +, then 2 again and + and 4 is displayed.

Press C, or run the program again, press 2, then +, then 2 again. Now rotate the device (Ctrl-F11 on the emulator) before pressing +, this time 2 is displayed.

Because the Activity was closed and recreated the running total was lost. We can solve this by preserving the total state variable. Add the following code to the main Activity class (notice the calls to the super class functions, these must be done).

    @Override
    public void onSaveInstanceState(Bundle outState) {
	super.onSaveInstanceState(outState);
	outState.putFloat("TOTAL", total);
    }
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
	super.onRestoreInstanceState(savedInstanceState);
	total=savedInstanceState.getFloat("TOTAL");
    }

Repeat the test and this time the running total is correctly displayed after the screen rotates.

It is strightforward to support saving state in Android, there are plenty of helpful methods on the Bundle class. Notice that the onCreate(Bundle) method also has access to the same Bundle, thus saving the need to code an onRestoreInstanceState(Bundle), however the code using Bundle in onCreate will need to check for null, for when onSaveInstanceState(Bundle) was not called (e.g. when a user exits an Application normally). In the example code here the overridden function onRestoreInstanceState(Bundle savedInstanceState) can be removed and replaced with this code at the end of the onCreate(Bundle savedInstanceState) function:

    if(savedInstanceState!=null)
       	total=savedInstanceState.getFloat("TOTAL");

onSaveInstanceState(Bundle) and onPause()

Use onPause to save data that should be preserved long term, use onSaveInstanceState for transient states. For example in a game the current score could be saved in onSaveInstanceState and only saved in onPause if it was good enough for the High Score table, in which cause the High Score table (in SharedPreferences, a database table, or a file) is updated.

onSaveInstanceState\onRestoreInstanceState and the Android Lifecycle

The SDK documentation says that onSaveInstanceState(Bundle) may run just before or just after onPause(). As for onRestoreInstanceState(Bundle) that occurs after onStart() and before onResume(), see Android Lifecycle and Activity LifeCycle Scenarios for Testing.

See Also:

[1]

[2]

Android Lifecycle

Activity LifeCycle Scenarios for Testing

No records found.