Introduction
This is the last post in my series about saving data in your Android application. This final post will explain when you should save the current state of your application so users do not lose their data. There are two types of states that can be saved: you can save the current state of the UI if the user is interrupted while entering data so he can resume input when the application is started again, or you can save the data to a more permanent store of data to access it at any time.
Saving the Current UI State
When another application is launched and hides your application, the data your user entered may not be ready to be saved to a permanent store of data yet since the input is not done. On the other hand, you should still save the current state of the activity so users don’t lose all their work if a phone call comes in for example. A configuration change with the Android device like rotating the screen will also have the same effect, so this is another good reason to save the state.
When one of those events or another event that requires saving the state of the activity occurs, the Android SDK calls the onSaveInstanceState
method of the current activity, which receives an android.os.Bundle
object as a parameter. If you use standard views from the Android SDK and those views have unique identifiers, the state of those controls will be automatically saved to the bundle. But if you use multiple instances of a view that have the same identifier, for example by repeating a view using a ListView
, the values entered in your controls will not be saved since the identifier is duplicated. Also, if you create your own custom controls, you will have to save the state of those controls.
If you need to manually save the state, you must override the onSaveInstanceState
method and add your own information to the android.os.Bundle
received as a parameter with pairs of key/values. This information will then be available later on when the activity needs to be restored in its onCreate
and onRestoreInstanceState
methods. All primitives types or arrays of values of those types can be saved to a bundle. If you want to save objects or an array of objects to the bundle, they must implement the java.io.Serializable
or android.os.Parcelable
interfaces.
To demonstrate saving to a state, I will use an upgraded version of the application used in the article about saving to a database that is available on GitHub at http://github.com/CindyPotvin/RowCounter. The application manages row counters used for knitting project, but it had no way to create a project. In the new version, the user can now create a new project, and the state of the project being created needs to be saved if the user is interrupted while creating the project. For demonstration purposes, numerical values are entered using a custom CounterView
control that does not handle saving the state, so we must save the state of each counter manually to the bundle.
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
CounterView rowCounterAmountView = (CounterView)this.findViewById(R.id.row_counters_amount);
savedInstanceState.putInt(ROW_COUNTERS_AMOUNT_STATE, rowCounterAmountView.getValue());
CounterView rowAmountView = (CounterView)this.findViewById(R.id.rows_amount);
savedInstanceState.putInt(ROWS_AMOUNT_STATE, rowAmountView.getValue());
super.onSaveInstanceState(savedInstanceState);
}
When the user navigates back to the application, the activity is recreated automatically by the Android SDK from the information that was saved in the bundle. At that point, you must also restore the UI state for your custom controls. You can restore the UI state using the data you saved to the bundle from two methods of your activity: the onCreate
method which is called first when the activity is recreated or the onRestoreInstanceState
method that is called after the onStart
method. You can restore the state in one method or the other and in most cases it won’t matter, but both are available in case some initialization needs to be done after the onCreate
and onStart
methods. Here are the two possible ways to restore the state from the activity using the bundle saved in the previous example:
@Override
protected void onCreate(Bundle savedInstanceState) {
[...Normal initialization of the activity...]
if (savedInstanceState != null) {
CounterView rowCounterAmountView;
rowCounterAmountView = (CounterView)this.findViewById(R.id.row_counters_amount);
rowCounterAmountView.setValue(savedInstanceState.getInt(ROW_COUNTERS_AMOUNT_STATE));
CounterView rowAmountView = (CounterView)this.findViewById(R.id.rows_amount);
rowAmountView.setValue(savedInstanceState.getInt(ROWS_AMOUNT_STATE));
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
CounterView rowCounterAmountView = (CounterView)this.findViewById(R.id.row_counters_amount);
rowCounterAmountView.setValue(savedInstanceState.getInt(ROW_COUNTERS_AMOUNT_STATE));
CounterView rowAmountView = (CounterView)this.findViewById(R.id.rows_amount);
rowAmountView.setValue(savedInstanceState.getInt(ROWS_AMOUNT_STATE));
}
Remember, saving data in a bundle is not meant to be a permanent store of data since it only stores the current state of the view: it is not part of the activity lifecyle and is only called when the activity needs to be recreated or it is sent to the background. This means that the onSaveInstanceState
method is not called when the application is destroyed since the activity state could never be restored. To save data that should never be lost, you should save the data to one of the permanent data store described earlier in this series. But when should this data be stored?
Saving Your Data to a Permanent Data Store
If you need to save data to a permanent data store when the activity is sent to the background or destroyed for any reason, your must save your data in the onPause
method of the activity. The onStop
method is called only if the UI is completely hidden, so you cannot rely on it being raised all the time. All the essential data must be saved at this point because you have no control on what happens after: the user may kill the application for example and the data would be lost.
In the previous version of the application, when the user incremented the counter for a row in a project, the application saved the current value of the counter to the database every time. Now, we’ll save the data only when the user leaves the activity, and saving at each counter press is no longer required:
@Override
public void onPause() {
super.onPause();
ProjectsDatabaseHelper database = new ProjectsDatabaseHelper(this);
for (RowCounter rowCounter: mRowCounters) {
database.updateRowCounterCurrentAmount(rowCounter);
}
Later on, if your application was not destroyed and the user accesses the activity again, there are two possible processes that can occurs depending on how the Android OS handled your activity. If the activity was still in memory, for example if the user opened another application and came back immediately, the onRestart
method is first called, followed by a call to the onStart
method and finally the OnResume
method is called and the activity is shown to the user. But if the activity was recycled and is being recreated, for example if the user rotated the device so the layout is recreated, the process is the same as the one for a new activity: the onCreate
method is first called, followed by a call to the onStart
method and finally the onResume
method is called and the activity is shown to the user.
So, if you want to use the data that was saved to a permanent data store to initialize controls in your activity that lose their state, you should put your code in the onResume
method since it is always called, regardless of whether the activity was recreated or not. In the previous example, it is not necessary to explicitly restore the data since no custom controls were used: if the activity was recycled, it is recreated from scratch and the onCreate
method initializes the controls from the data in the database. If the activity is still in memory, there is nothing else to do: the Android SDK handles showing the values as they were first displayed as explained earlier in the section about saving UI states. Here is a reminder of what happens in the onCreate
method:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.project_activity);
Intent intent = getIntent();
long projectId = intent.getLongExtra("project_id", -1);
ProjectsDatabaseHelper database = new ProjectsDatabaseHelper(this);
Project currentProject = database.getProject(projectId);
TextView projectNameView = (TextView)findViewById(R.id.project_name);
projectNameView.setText(currentProject.getName());
ListView rowCounterList = (ListView)findViewById(R.id.row_counter_list);
mRowCounters = currentProject.getRowCounters();
ListAdapter rowCounterListAdapter = new RowCounterAdapter(this,
R.layout.rowcounter_row,
currentProject.getRowCounters());
rowCounterList.setAdapter(rowCounterListAdapter);
}
This concludes the series about saving data in your Android application. You now know about the various types of data storage that are available for Android applications and when they should be used so your users never lose their data and have the best user experience possible.