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.

Lessons Learned from our Associate Developer
Team

Lessons Learned from our Associate Developer

September 13, 2023

One of our Associate Software Developers, Rohit, reflects on his time at MichiganLabs working on a short-term project, what he learned about real-world development, and the software consultancy business model.

Read more
Web app vs. mobile app: How to decide which is best for your business
Business Development

Web app vs. mobile app: How to decide which is best for your business

March 26, 2024

When considering whether to develop a web app or a mobile app for your business, there’s honestly no definitive answer. But this article will help you make an informed decision that aligns with your business goals and sets you up for success.

Read more
UX Writing Tips
Design Process

UX Writing Tips

February 3, 2023

Kai shares a few tips he's collected on how to write for user interfaces.

Read more
View more articles