Product Strategy
November 22, 2022A look at Product Strategy at MichiganLabs. Why we do it, what it is, what it is not, and how we approach it.
Read moreWith 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.
A look at Product Strategy at MichiganLabs. Why we do it, what it is, what it is not, and how we approach it.
Read moreLearn how to use Android's Jetpack Compose to navigate from a screen inside of one NavigationBarItem to another.
Read moreKourtney examines the Burning Man Festival and its fascinating history of human-centered design
Read more