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.

Building maintainable Android applications

Why should you care about maintainability when everything already works? It’s not a bad thing to try out new things with prototypes to increase your knowledge, but when you start building applications that will be used for many years and that will grow larger and more complex, you want to make sure you can keep adding to it without running into problems. When many people need your software to do their work every day, you cannot afford to build something that will collapse under it’s own weight. So, here are a few guidelines to help you:

Keep your code clean and documented
When you have to go back regularly to modify your old code, having clear names and documentation is very valuable. You only write a piece of code one, but it will be read again many times so you should make that job as easy as possible. Having a consistent coding standard also helps a lot with readability: it is jarring to read code that jumps from one coding style to another, and you want to concentrate on the code and not how it looks.

Also, I don’t believe that all code can be self-documenting code. It’s a good thing to aim for, but sometimes you have to do hacks that are not as clean: a little explanation can go a long way when you try understanding it again a few years down the road. In other cases, for example when you implement a complex bit of business logic, you will need to know what the code was supposed to do if it does not behave as expected or if the logic needs to be changed later on.

Don’t do complicated when you can do simple
In keeping with good Agile principles, you don’t want to plan everything that’s even going to happen in you application before getting started. Don’t get me wrong, you should have an idea of where you’re going and have a basic architecture planned out, but keep thing as simple as possible while still being organized. You can always refactor a complex bit of code to add one more layer of abstraction as needed and optimize the bits that are not as effective, but having a relatively simple architecture planned out helps keep everything clean and makes it easy to introduce new team members to a project.

You should also avoid clutter in your code base. First, fix as many compilation warnings as you can: some may not be important to you, but you don’t want to risk missing a new warning that shows a real problem. It’s something small and silly, but it makes it harder every time you need to modify something. Also, only implement the methods you need and don’t keep old code lying around to limit the amount of code you have to maintain. If you need to reuse the code you deleted later on, you can always retrieve it from your source control system.

Limit the number of external libraries
There are many good libraries available in the Android and Java worlds. But while using a library can save you time, it also has has a real cost: when you upgrade your application to support the newest Android version, you also need to upgrade all the libraries you use. Each dependency is a part of your project that can break and on which you have no direct control. Libraries can also stop being upgraded, which will eventually require a re-factoring to an alternative or an effort from your part to maintain the old library, which can be be costly.

On the other hand, using a library it can be worth it if you need to make a quick prototype or to add a component that would take too long to built. But for mission-critical stuff that can make or break your application, you may be better off making your own implementation based on your needs. Fortunately, nothing is stopping you from learning from the existing libraries to make your own thing. You may also prefer to use methods from the standard Android SDK version even if their implementation is not the best: it is the most likely to have been tested on a large range of devices and to be maintained for a long time.

Your product is not your debug version
If your code works when loaded from your development machine to your testing device that’s great but it’s not the end of your job. You need to test your release version on at least a few more device and Android version to be sure it will work when released to the store, since not everyone that will use your application has the same device and OS version than you. You also need to keep testing when new versions of the Android OS keep coming out so it does not break for your users that like to keep their device updated with the latest and greatest.

Also, if your application is not deployed via the Google Play store, you may also wish to add some form of debugging to your application, for example a logging mode that can be toggled on to report a problem to you: it can be pretty hard to help a user when the only information you have is that your application crashed. Finally, you also need to have some documentation ready for your users, unless your application is very simple. A little FAQ on your website may be enough to do the job, but don’t leave your user totally in the dark trying to understand your application. This will reduce the number of support requests and give you a few answers ready to go when the questions start coming in.

Saving to a SQLite database in your Android application

This is the fourth post in my series about saving data in Android applications. Here are the other posts :

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

The previous posts described how to save files to the file system and to the preferences files. This can be enough if for a simple application, but if you data has a complex structure or if you have a lot of data to save, using a database is a better option. Managing a database requires more knowledge and setup, but it comes with many validations and performance optimization. The Android SDK includes the open source SQLite database engine and the classes needed to access it.

SQLite is a self-contained relational database that requires no server to work. The database itself is saved to a file in the internal storage of your application, so each application has its own private database that is not accessible to other applications. You can learn more about the SQLite project itself and its implementation of the SQL query language at http://www.sqlite.org.

New to databases? A relational database saves data to tables. Each table is made of columns, and for each column you must choose a name and the type of data that can be saved in it. Each table should also have a column or many column that are set as the key of the table so each row of data can be uniquely identified. Relationships can also be defined between tables.
 
The basics of databases and the SQL query language used by most databases could take may articles to explain. If you don’t know how to use a database, this is a subject worth learning more aboutsince databases are used in almost all applications to store data.

To demonstrate how to create a database and interact with it, I created a small sample application, which is available at http://github.com/CindyPotvin/RowCounter. The application is a row counter for knitting projects: the user can create a knitting project containing one or many counters used to track the current number of rows done and to show the total amount of rows to reach. The structure of the database is as follow, with a project table in relation with a row_counter table :
rowcounter
First, to be able to create the database, we need a contract class for each table that describes the name of the elements of the table. This class should be used each time the name of elements in the database is required. To describe the name of each column, the contract class also contains a subclass with an implementation of the android.provider.BaseColumn, which automatically adds the name of an_ID and of a _COUNT column. I also like to put the CREATE TABLE SQL query in the contract class so all the strings used in SQL queries are at the same place. Here is the contract class for the row_counter table in the example :

/**
* This class represents a contract for a row_counter table containing row
* counters for projects. The project must exist before creating row counters
* since the counter have a foreign key to the project.
*/
public final class RowCounterContract {

/**
* Contains the name of the table to create that contains the row counters.
*/
public static final String TABLE_NAME = "row_counter";

/**
* Contains the SQL query to use to create the table containing the row counters.
*/
public static final String SQL_CREATE_TABLE = "CREATE TABLE "
+ RowCounterContract.TABLE_NAME + " ("
+ RowCounterContract.RowCounterEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ RowCounterContract.RowCounterEntry.COLUMN_NAME_PROJECT_ID + " INTEGER,"
+ RowCounterContract.RowCounterEntry.COLUMN_NAME_CURRENT_AMOUNT + " INTEGER DEFAULT 0,"
+ RowCounterContract.RowCounterEntry.COLUMN_NAME_FINAL_AMOUNT + " INTEGER,"
+ "FOREIGN KEY (" + RowCounterContract.RowCounterEntry.COLUMN_NAME_PROJECT_ID + ") "
+ "REFERENCES projects(" + ProjectContract.ProjectEntry._ID + "));";

/**
* This class represents the rows for an entry in the row_counter table. The
* primary key is the _id column from the BaseColumn class.
*/
public static abstract class RowCounterEntry implements BaseColumns {

   // Identifier of the project to which the row counter belongs
   public static final String COLUMN_NAME_PROJECT_ID = "project_id";

   // Final amount of rows to reach
  public static final String COLUMN_NAME_FINAL_AMOUNT = "final_amount";

   // Current amount of rows done
   public static final String COLUMN_NAME_CURRENT_AMOUNT = "current_amount";
   }
}

To create the tables that stores the data described by the contracts, you must implement the android.database.sqllite.SQLLiteOpenHelper class that manages the access to the database. The following methods should be implemented as needed:

  • onCreate: this method is called the first time the database is opened by your application. You should setup the database for use in that method by creating the tables and initializing any data you need.
  • onUpdate: this method is called when your application is upgraded and the version number has changed. You don’t need to do anything for your first version, but in the following versions you must provide queries to modify the database from the old version to the new structure as needed so your user don’t loose their data during the upgrade.
  • onDowngrade (optional) : you may implement this method if you want to handle the case where your application is downgraded to a version requiring an older version. The default implementation will throw a SQLiteException and will not modify the database.
  • onOpen (optional) : this method is called after the database has been created, upgraded to a newer version or downgraded to an older version.

Here is a basic implementation of the android.database.sqllite.SQLLiteOpenHelper for the example that executes an SQL CREATE TABLE query for each table of the database in the onCreate method. There is no method available in the android.database.sqlite.SQLiteDatabase class to create a table, so you must use the execSQL method to execute the query.

/**
* This class helps open, create, and upgrade the database file containing the
* projects and their row counters.
*/
public class ProjectsDatabaseHelper extends SQLiteOpenHelper {
   // If you change the database schema, you must increment the database version.
   public static final int DATABASE_VERSION = 1;
   // The name of the database file on the file system
   public static final String DATABASE_NAME = "Projects.db";

   public ProjectsDatabaseHelper(Context context) {
      super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }

   /**
    * Creates the underlying database with the SQL_CREATE_TABLE queries from
    * the contract classes to create the tables and initialize the data.
    * The onCreate is triggered the first time someone tries to access
    * the database with the getReadableDatabase or
    * getWritableDatabase methods.
    *
    * @param db the database being accessed and that should be created.
    */
    @Override
   public void onCreate(SQLiteDatabase db) {
      // Create the database to contain the data for the projects
      db.execSQL(ProjectContract.SQL_CREATE_TABLE);
      db.execSQL(RowCounterContract.SQL_CREATE_TABLE);
      initializeExampleData(db);
      }

   /**
    * This method must be implemented if your application is upgraded and must
    * include the SQL query to upgrade the database from your old to your new
    * schema.
    *
    * @param db the database being upgraded.
    * @param oldVersion the current version of the database before the upgrade.
    * @param newVersion the version of the database after the upgrade.
    */
   @Override
   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      // Logs that the database is being upgraded
      Log.i(ProjectsDatabaseHelper.class.getSimpleName(),
            "Upgrading database from version " + oldVersion + " to " + newVersion);
      }
   }

Once the android.database.sqllite.SQLLiteOpenHelper is implemented, you can get an instance of the database object android.database.sqlite.SQLiteDatabase using the getReadableDatabase method of the helper if
you only need to read data or the getWritableDatabase method if you need to read and write data. There are four kinds of basic operations that can be done with the data, and modifications can not be undone like in all databases.

  • Inserting a new row:the insert method of the android.database.sqlite.SQLiteDatabase object inserts a new row of data in a table. Data can be inserted with a SQL INSERT query using the execSQL method, but using insert is recommended to avoid SQL injection: only one database row can be created by the insert method and nothing else, regardless of the input. In the following example, a few test projects are initialized in the database of the application by the onCreate method of the database helper after the creation of the table:
    /**
     * Initialize example data to show when the application is first installed. 
     * 
     * @param db the database being initialized.
     */
    private void initializeExampleData(SQLiteDatabase db) {
       // A lot of code is repeated here that could be factorized in methods, 
       // but this is clearer for the example
    		
       // Insert the database row for an example project in the project table in the
       // database
       long projectId;
       ContentValues firstProjectValues = new ContentValues();
       firstProjectValues.put(ProjectContract.ProjectEntry.COLUMN_NAME_TITLE, 
                              "Flashy Scarf");
       projectId = db.insert(ProjectContract.TABLE_NAME, null, firstProjectValues);
       // Insert the database rows for a row counter linked to the project row 
       // just created in the database (the insert method returns the 
       // identifier of the row)
       ContentValues firstProjectCounterValues = new ContentValues();
       firstProjectCounterValues.put(RowCounterContract
                                       .RowCounterEntry.COLUMN_NAME_PROJECT_ID, projectId);
       firstProjectCounterValues.put(RowCounterContract
                                       .RowCounterEntry.COLUMN_NAME_FINAL_AMOUNT, 120);
       db.insert(RowCounterContract.TABLE_NAME, null, firstProjectCounterValues);
    		
       // Insert the database row for a second example project in the project 
       // table in the database.
       ContentValues secondProjectValues = new ContentValues();
       secondProjectValues.put(ProjectContract.ProjectEntry.COLUMN_NAME_TITLE, 
                               "Simple Socks");
       projectId = db.insert(ProjectContract.TABLE_NAME, null, secondProjectValues);
       // Insert the database rows for two identical row counters for the 
       // project in the database
       ContentValues secondProjectCounterValues = new ContentValues();
       secondProjectCounterValues.put(RowCounterContract
                                        .RowCounterEntry.COLUMN_NAME_PROJECT_ID, projectId);
       secondProjectCounterValues.put(RowCounterContract
                                        .RowCounterEntry.COLUMN_NAME_FINAL_AMOUNT, 80);
       db.insert(RowCounterContract.TABLE_NAME, null, secondProjectCounterValues);
       db.insert(RowCounterContract.TABLE_NAME, null, secondProjectCounterValues);	
       }	
    
  • Reading existing rows: the query method from the android.database.sqlite.SQLiteDatabase class retrieves the data that was previously inserted in the database. This method will return a cursor that points to the collection of rows returned by your request, if any. You can then convert the data fetched from the database table to an object can be used in your application: in the example, the rows from the project table are converted to Project objects.
    /**
    
    * Gets the list of projects from the database.
    *
    * @return the current projects from the database.
    */
    public ArrayList getProjects() {
       ArrayList projects = new ArrayList();
       // Gets the database in the current database helper in read-only mode
       SQLiteDatabase db = getReadableDatabase();
    
       // After the query, the cursor points to the first database row
       // returned by the request.
       Cursor projCursor = db.query(ProjectContract.TABLE_NAME, null, null, 
                                    null, null, null, null);
       while (projCursor.moveToNext()) {
          // Get the value for each column for the database row pointed by
          // the cursor using the getColumnIndex method of the cursor and
          // use it to initialize a Project object by database row
          Project project = new Project();
          
          int idColIndex = projCursor.getColumnIndex(ProjectContract.ProjectEntry._ID);
          long projectId = projCursor.getLong(idColIndex);
          project.setId(projCursor.getLong(projectId);
    
          int nameColIndex = projCursor.getColumnIndex(ProjectContract
                                                        .ProjectEntry.COLUMN_NAME_TITLE);
          project.setName(projCursor.getString(nameColIndex));
          // Get all the row counters for the current project from the
          // database and add them all to the Project object
         project.setRowCounters(getRowCounters(projectId));
    
         projects.add(project);
         }
    
       projCursor.close();
    
       return (projects);
       }
    
  • Updating existing rows: the update method of an instance of the android.database.sqlite.SQLiteDatabase class updates the data in a row or in multiple rows of a database table. Like with the insert method, you could use the execSQL query to run a SQL UPDATE query, but using the update method is safer. In the following example, the current row counter value for the row counter in the row_counter table is updated with the new value. According to the condition specified only the row counter with the identifier passed as a parameter is updated but with another condition you could update many rows, so you should always make sure that the condition only selects the rows you need.
    /**
     * Updates the current amount of the row counter in the database to the value 
     * in the object passed as a parameter.
     * 
     * @param rowCounter the object containing the current amount to set.
     */
    public void updateRowCounterCurrentAmount(RowCounter rowCounter) {
       SQLiteDatabase db = getWritableDatabase();
    		
       ContentValues currentAmountValue = new ContentValues();
       currentAmountValue.put(RowCounterContract.RowCounterEntry.COLUMN_NAME_CURRENT_AMOUNT, 
                              rowCounter.getCurrentAmount());
    		
       db.update(RowCounterContract.TABLE_NAME, 
    	     currentAmountValue, 
    	     RowCounterContract.RowCounterEntry._ID +"=?",
    	     new String[] { String.valueOf(rowCounter.getId()) });
       }
    
  • Deleting existing rows:the delete method of an instance of the android.database.sqlite.SQLiteDatabase class deletes a row or in multiple rows of a database table. Like with the insert method, you could use the execSQL query to run a SQL UPDATE query, but using the delete method is safer. In the following example, a row counter in the row_counter table is deleted. According to the condition specified only the row counter with the identifier passed as a parameter is deleted but with another condition you could delete many rows, so you should always make sure that the condition only selects the rows you need so you don’t delete too much data.
    /**
     * Deletes the specified row counter from the database.
     * 
     * @param rowCounter the row counter to remove.
     */
    public void deleteRowCounter(RowCounter rowCounter) {
       SQLiteDatabase db = getWritableDatabase();
    		
       db.delete(RowCounterContract.TABLE_NAME, 			  
                 RowCounterContract.RowCounterEntry._ID +"=?",
                 new String[] { String.valueOf(rowCounter.getId()) });
       }
    

Finally, if you want to encapsulate access to the data in your database to avoid calling the database helper directly in your activity, you can also implement the android.content.ContentProvider class from the Android SDK. This is only required if your application must share data with other applications: you do not need one to get started, but you should consider using it as your data gets more complex.