Android Core

Storing the state of an activity of your Android application

This is the last post in my series about saving data in your Android application. The previous posts went over the various way to save data in your application:

Introduction : How to save data in your Android application
Saving data to a file in your Android application
Saving preferences in your Android application
Saving to a SQLite database in your Android application

This final post will explain when you should save the current state of your application so you 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 the user 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 event 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 purpose, numerical values are entered using a custom CounterView control that does no 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());

   // Call the superclass to save the state of all the other controls in the view hierarchy
   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 the 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 it 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...]

   // Check if a previously destroyed activity is being recreated.
   // If a new activity is created, the savedInstanceState will be empty
   if (savedInstanceState != null) {
      // Restore value of counters from saved state
      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) {
   // Call the superclass to restore the state of all the other controls in the view hierarchy
   super.onRestoreInstanceState(savedInstanceState);

   // Restore value of counters from saved stat
   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 send 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);
	    
   // Update the value of all the counters of the project in the database since the activity is
   // destroyed or send to the background
   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 if 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 initialize 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);
		
   // Gets the database helper to access the database for the application
   ProjectsDatabaseHelper database = new ProjectsDatabaseHelper(this);
   // Use the helper to get the current project
   Project currentProject = database.getProject(projectId);

   TextView projectNameView = (TextView)findViewById(R.id.project_name); 
   projectNameView.setText(currentProject.getName());
	      
		// Initialize the listview to show the row counters for the project from 
		// the database
   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 should they be used so your users never lose their data and have the best user experience possible.

Cindy Potvin

Cindy is a programmer with over 5 years of experience developing web applications. She also works on Android applications and share her knowledge via her blog and on Twitter.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Moses
6 years ago

okay now i get it. That means our data needs to be saved on the device SQLite database and we do that by Overriding onPause(), and we retrieve back the data by Overriding onResume()?

Back to top button