How to save/restore Activity's and Fragment's state

How to save/restore Activity's state

android_save_state.png

In an application, it is essential to be able to save user context between pauses. For example, it may me incoming call, or start of any other activity. And of course, after this pause, the user will want to return to the previous application state before pause.

The saved data that the system uses to restore the previous state is called the instance state and is a collection of key-value pairs stored in a Bundle object.

To achieve that in Android, you must be aware about Activity lifecycle. Activity lifecycle is defined by Android OS and it lets you to interact at some specific point via methods.

Following diagram sums this lifecycle

Android activity life cycle

This diagram presents different states in which an application can be and also methods called in Activity instance by OS when it passes from one state to another.

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 users would be annoyed if input haven been lost. Android have a pair of methods called onSaveInstanceState and onRestoreInstanceState which are used by Views automatically to save their data. These methods only work if the Views with data can be identified, supplied by the android:id attribute. This method pair can be overridden in an Activity so that state variables not associated with input fields can also be saved and restored.

Activity class has two methods you must override when you want to save activity state. Values saved are stored in a Bundle object that is essentially a key-value pairs map and passed into the onCreate method of every Android Activity. In the Bundle you can save user selections (for example, tab), scroll view positions, user-submitted data (for example, any form data).

  • onSaveInstanceState method is the moment where you can save values you want be restored when activity will be recreated after a pause. When this method is called, any state-related data should be placed into the Bundle. This method is called when an Activity is being backgrounded (either after onPause() or onStop(), depending on different factors). The default implementation of this method saves information about the state of the activity’s view hierarchy, such as the text in an EditText widget or the scroll position of a ListView.
  • onRestoreInstanceState method is the moment where you can get stored values and restore saved state for current Activity.

Before your app is suspended, the onSaveInstanceState() method of your Activity will be fired, followed by onPause(). You should place code in onSaveInstanceState() that saves any UI state that you don't want to lose. Other data, that does not relate to UI state, should be saved in onPause(). Here you should save data to your own storage mechanisms, such as a SQLite database or a file.

Following code lets you accomplish the job

...
boolean isHidden;
double rating;
int year;
String title;
final String IS_HIDDEN = "IS_HIDDEN";
final String RATING = "RATING";
final String YEAR = "YEAR";
final String TITLE = "TITLE";
...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);

    savedInstanceState.putString(TITLE, "Gladiator");
    savedInstanceState.putBoolean(IS_HIDDEN, false);
    savedInstanceState.putDouble(RATING, 8.5);
    savedInstanceState.putInt(YEAR, 2000);

    // you can also save serializable objects
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);

    title = savedInstanceState.getString(TITLE);
    isHidden = savedInstanceState.getBoolean(IS_HIDDEN);
    rating = savedInstanceState.getDouble(RATING);
    year = savedInstanceState.getInt(YEAR);
}

Activities have the ability, under special circumstances, to restore themselves to a previous state using the data stored in the Bundle. If there is no available instance data, the savedInstanceState will be null. For example, the savedInstanceState will always be null the first time an Activity is started, but may be non-null if an Activity is destroyed during rotation.

Because the onCreate() method is called whether the system is creating a new instance of your activity or recreating a previous one, you must check whether the state Bundle is null before you attempt to read it. If it is null, then the system is creating a new instance of the activity, instead of restoring a previous one that was destroyed.

For example, here’s how you can restore some state data in onCreate().

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
        title = savedInstanceState.getString(TITLE);
        rating = savedInstanceState.getDouble(RATING);
    } else {
        // initialize members with default values for a new instance
    }
}

Instead of restoring the state during onCreate() you may choose to implement onRestoreInstanceState(), which the system calls after the onStart() method. The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null.

public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);

    title = savedInstanceState.getString(TITLE);
    rating = savedInstanceState.getDouble(RATING);
}

So, use onSaveInstanceState() for transient states, use onPause() to save data that should be preserved long term.

How to save/restore Fragment's state

When Androis OS destroy Fragment everything will just happen exactly the same as Activity. It means that every single member variables are also destroyed. You have to manually save and restore those variables through onSaveInstanceState and onActivityCreated method respectively. Note that there is no onRestoreInstanceState method inside Fragment.

public class MainFragment extends Fragment {
    private String title;
    private double rating;
    private int year;

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);

        savedInstanceState.putString(TITLE, "Gladiator");
        savedInstanceState.putDouble(RATING, 8.5);
        savedInstanceState.putInt(YEAR, 2000);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        title = savedInstanceState.getString(TITLE);
        rating = savedInstanceState.getDouble(RATING);
        year = savedInstanceState.getInt(YEAR);
    }
}

Useful links

comments powered by Disqus