Modular Migrations for Android Applications

July 14, 2015

With Android applications, there are two cases where the internal database for the app will need to be changed:

A user has just installed the app for the first time (or has cleared their application data), so no database currently exists.
A user has just upgraded their app to a new version that uses a newer database structure.

Using database migrations, we can handle both of these cases seamlessly. Database migrations are a convenient and consistent way to manage the structure of your database over time. You might think of them as a version control system (like Git) where migration “patches” are applied in a sequence to bring the target database up to date with the current version. This sequence of migrations can be applied to a target database of any version because each migration knows how to upgrade the target from its current version to the next version in the sequence, eventually reaching the most recent version. For the first case where the user has no database, this can be considered version 0. From that perspective, our two use cases become essentially the same.
In this example app, we will be using the OrmLiteSqliteOpenHelper abstract class from ORMLite which we extend to create a DatabaseHelper class that will handle running migrations and interacting with the application. OrmLiteSqliteOpenHelper defines two abstract methods we must implement:

public abstract class OrmLiteSqliteOpenHelper {
 // What to do when your database needs to be created.
 // This is the version=0 case
 public abstract void onCreate(SQLiteDatabase db, ConnectionSource connectionSource);

 // What to do when your database needs to be updated.
 // This is the version=x..N case
 public abstract void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion);
}

Since we are thinking about our “no database” case as just version=0, we can implement the onCreate method easily by handing off the task to the onUpgrade method:

@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
 onUpgrade(db, connectionSource, 0, DATABASE_VERSION);
}

Upgrading from version=X to version=N case is just applying a linear sequence of migration “patches”, so the onUpgrade method is also very simple:

@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
 while (oldVersion++ < newVersion) {
 Migration m = VersionManager.getMigration(oldVersion);
 if (!m.doUpgrade(db, connectionSource)) {
 // Unsuccessful, stop migrations
 return;
 }
 }
}

The Migration class defines the steps necessary to alter the database from one version to the next. VersionManager simply contains a list of all the migrations in the order they should be run.
To create a new migration, subclass Migration and implement its abstract methods:

public class V1_AddExampleTable extends Migration {
 public String getDescription() {
 return "Add sample table";
 }

 public int getVersion() {
 return 1;
 }

 public void upgrade(SQLiteDatabase db, ConnectionSource connectionSource) throws SQLException {
 TableUtils.createTable(connectionSource, Example.class);
 }
}

Next, add the migration to the VersionManager‘s list of migrations:

public class VersionManager {
 private static Migration[] migrations = {
 new V1_AddExampleTable(),
 };
}

You may notice that DatabaseHelper.onUpgrade calls Migration.doUpgrade instead of the upgrade method we defined. This is because the Migration base class wraps the upgrade steps in a transaction which allows any changes made during the migration to be rolled-back in case something goes wrong.
When the application connects to the database, you should see the migration process logging its steps to logcat:

W//DatabaseHelper.java:35: main Creating new database using migrations
W//DatabaseHelper.java:41: main Upgrading db from v0 to v1...
I//Migration.java:12: main Running migration to v1: "Add example table"
I/TableUtils: creating table `example`
I/TableUtils: executed create table statement changed 1 rows: CREATE TABLE `example` (`id` INTEGER PRIMARY KEY AUTOINCREMENT )
I//Migration.java:32: main Migration to v1 OK!
I//DatabaseHelper.java:49: main Database upgrade complete!

Let’s add a column to the example table using the migration method:

public class V2_AddTextToExample extends Migration {
 public String getDescription() {
 return "Add text column to example table";
 }

 public int getVersion() {
 return 2;
 }

 public void upgrade(SQLiteDatabase db, ConnectionSource connectionSource) throws SQLException {
 try {
 DaoManager.lookupDao(connectionSource, Example.class)
 . executeRaw(
 "ALTER TABLE `" + DatabaseTableConfig.extractTableName(Example.class) + "`"
 + " ADD COLUMN " + Example.TEXT + " VARCHAR;"
 );
 } catch (SQLException e) {
 // Field might have been added by V1 migration when creating a new database
 Ln.i("example.text field already exists");
 }
 }
}

When the new version of the application is run, OrmLite will detect that the version of the database on the device does not match the current DATABASE_VERSION defined by the application code and will call DatabaseHelper.onUpgrade:

W//DatabaseHelper.java:34: main Upgrading db from v1 to v2...
I//Migration.java:12: main Running migration to v2: "Add text column to example table"
I//Migration.java:32: main Migration to v2 OK!
I//DatabaseHelper.java:42: main Database upgrade complete!

The final result is an implementation of DatabaseHelper that is cleaner, easier to understand, and more modular than the example given in the OrmLite docs, which suggests putting all the upgrade code inside the onUpgrade method.

Looking for more like this?

Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.

Chicago Roboto 2022 Retrospective
Android Development

Chicago Roboto 2022 Retrospective

August 11, 2022

Scott Schmitz shares some notes of interest from talks at Chicago Roboto 2022, an Android community conference, that took place August 1-2.

Read more
From bits to qubits: The future of quantum computing
Development

From bits to qubits: The future of quantum computing

July 10, 2024

Learn how quantum computing, which uses qubits capable of representing both 0 and 1 simultaneously, revolutionizes data processing. Discover the impact it could have on industries like finance and pharmaceuticals by enhancing risk assessment, fraud detection, and drug discovery.

Read more
Three principles for creating user-friendly products
Business Design

Three principles for creating user-friendly products

January 25, 2023

Grayson discusses three guiding principles he utilizes when designing user experiences for products.

Read more
View more articles