Saving an activity's state
The mobile environment is very dynamic, with users changing tasks much more often than on desktops. With generally fewer resources on a mobile device, it should be expected that your application will be interrupted at some point. It's also very possible that the system will shut down your app completely to give additional resources to the task at hand. It's the nature of mobiles.
A user might start typing something in your app, be interrupted by a phone call, or switch over to another app to send a text message, and by the time they get back to your app, the system may have closed it down completely to free up the memory. To provide the best user experience, you need to expect such behavior and make it easier for your user to resume from where they left off. The good thing is that the Android OS makes this easier by providing callbacks to notify your app of state changes.
Note
Simply rotating your device will cause the OS to destroy and recreate your activity. This might seem a bit heavy-handed, but it's done for good reason—it's very common to have different layouts for portrait and landscape, so this ensures that your app is using the correct resources.
In this recipe, you'll see how to handle the onSaveInstanceState()
and onRestoreInstanceState()
callbacks to save your application's state. We will demonstrate this by creating a counter variable and increment it each time the Count button is pressed. We will also have an EditText
and a TextView
widget to see their default behavior.
Getting ready
Create a new project in Android Studio and name it StateSaver
. We need only a single activity, so the autogenerated main activity is sufficient. However, we will need a few widgets, including EditText
, Button
, and TextView
. Their layout (in activity_main.xml
) will look like this:
<EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentStart="true"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Count" android:onClick="onClickCounter"/> <TextView android:id="@+id/textViewCounter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/button"/>
How to do it...
Perform the following set of steps:
- To keep track of the counter, we need to add a global variable to the project, along with a key for saving and restoring. Add the following code to the
MainActivity.java
class:static final String KEY_COUNTER = "COUNTER"; private int mCounter=0;
- Then add the code needed to handle the button press; it increments the counter and displays the result in the
TextView
widget:public void onClickCounter(View view) { mCounter++; ((TextView)findViewById(R.id.textViewCounter)).setText("Counter: " + Integer.toString(mCounter)); }
- To receive notifications of application state change, we need to add the
onSaveInstanceState()
andonRestoreInstanceState()
methods to our application. OpenMainActivity.java
and add the following:@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_COUNTER,mCounter); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mCounter=savedInstanceState.getInt(KEY_COUNTER); }
- Run the program and try changing the orientation to see how it behaves (if you're using the emulator, Ctrl + F11 will rotate the device).
How it works...
All activities go through multiple states during their lifetime. By setting up callbacks to handle the events, we can have our code save important information before the activity is destroyed.
Step 3 is where the actual saving and restoring occurs. The system sends a Bundle (a data object that also uses name/value pairs) to the methods. We use the onSaveInstanceState()
callback to save the data and pull it out in the onRestoreInstanceState()
callback.
But wait! Did you try typing text in the EditText
view before rotating the device? If so, you'd have noticed that the text was also restored, but we don't have any code to handle that view. By default, the system will automatically save the state, provided it has a unique ID (not all views automatically have their state saved, such as the TextView
, but we can manually save it if we want).
Tip
Note that if you want Android to automatically save and restore the state of a view, it must have a unique ID (specified with the android:id=
attribute in the layout). Beware; not all view types automatically save and restore the state of a view.
There's more...
The onRestoreInstanceState()
callback is not the only place where the state can be restored. Look at the signature of onCreate()
:
onCreate(Bundle savedInstanceState)
Both methods receive the same Bundle
instance named savedInstanceState
. You could move the restore code to the onCreate()
method and it would work the same. But one catch is that the savedInstanceState
bundle will be null if there is no data, such as during the initial creation of the activity. If you want to move the code from the onRestoreInstanceState()
callback, just check to make sure that the data is not null, as follows:
if (savedInstanceState!=null) { mCounter = savedInstanceState.getInt(KEY_COUNTER); }
See also
- The Storing persistent activity data recipe will introduce persistent storage.
- Take a look at Chapter 6, Working with Data, for more examples on Android activities.
- The Understanding the activity lifecycle recipe explains the Android Activity Lifecycle.