Working with Migrations in Room Persistence Library

Henry "Dru" Onyango
4 min readSep 23, 2019

If you’ve worked persisting data on Android applications, then you are probably familiar with ORM libraries (Object-Relational Mapping) libraries.

ORMs make it easier to write SQL queries using the object-oriented paradigm as opposed to directly using the SQL language. Mario Hoyos wrote a great article about ORMs that you can read here. Further reading on ORMs can be found here.

In Android, some of the most common ORMs are Realm and Room. I’ve never really worked with Realm but I’d assume the idea is almost the same around how to use them.

Having worked with Room for a while now, one of the most common things you’ll face is what to do when your schema changes. Suppose you delete a column in a table, or you insert a new column or change a column name how to do you handle it.

In this article I’ll work you through a simple migration using Kotlin.

The first thing when working with Room is to create a Singleton of your database. Using the Singleton pattern, you prevent multiple instances of the same database being created on the app which would otherwise cause memory leaks.

@Database(
entities = [
Users::class
],
version = 1,
exportSchema = false)
@TypeConverters( )
abstract class AppDatabase : RoomDatabase(){
abstract fun usersDao(): UsersDao

companion object {
@Volatile private var instance: AppDatabase?=null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK){
instance ?: buildDatabase(context).also { instance = it}
}

private fun buildDatabase(context: Context) = androidx.room.Room.databaseBuilder(context,
AppDatabase::class.java, "users.database")
.build()
}
}

The above code shows how to set up a single instance of your database. Part of the configuration involves setting up the abstract classes for the DAOs (Data Access Objects) which I won’t go into.

Next, we create the User table. This is the table on which we are going to make the alternations.

@Entity(tableName = "users_table")data class Users(@PrimaryKey(autoGenerate = true)
var id: Int,
var name: String?,var email: String?)

The way our User Table is set up is such that it has an auto-generated primary key, a name of type String and an email address also of type String. I won’t go into writing the DAO for this table.

Suppose now, we changed the column “name” to “user_name” like so:

...//var user_name: String?,..//

If ran our application after that, we will get the error below:

Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. 
You can simply fix this by increasing the version number.

As the error report says, the first thing we need to do is upgrade the database version number:

@Database(
entities = [
Users::class
],
version = 2, // change this here to a number higher than 1
exportSchema = false)

Doing that would still result in an error where Room would not be able to establish the authenticity of the data since the schema is still different and no migrations have been supplied.

Quick Fix using destructive migration

A quick fix for this would be using destructive migrations. When we use destructive migrations, essentially what we are telling Room is to destroy and recreate all the tables. The problem with this is that our users would lose all their data. This would work if you are still in the development phase and you are pretty much the only one using/testing the application.

...//
private fun buildDatabase(context: Context) = androidx.room.Room.databaseBuilder(context,
AppDatabase::class.java, "users.database")
.fallbackToDestructiveMigration() // destructive migration
.build()
}
...//

Implementing migrations to save previous data

If you do not want the user to lose their data when an update is made, the best option is to right migrations. I know, migrations…

When I first heard of it, I thought it was going to be difficult but the truth is that Room makes doing these migrations seamless and easy.

So let’s take using the previous example of altering the column name from “name” to “user_name” in the Users table.

The migration would look something like this:

...//
// migration to alter the user_name in Users table
private val MIGRATION_1_2 = object : Migration(1,2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE users_table RENAME name TO user_name")
}
}
...//

After this, the next thing for you to is adding the migrations to your database.

A few things to note:
1. The name MIGRATION_1_2 can be anything really but it’s a convention to name it and append the previous and the next version number for the database
2. Migration(1,2) however has to match the previous and the next version number for your database

Okay, so now let’s add the migration to our database:

...//
private fun buildDatabase(context: Context) = androidx.room.Room.databaseBuilder(context,
AppDatabase::class.java, "users.database")
.addMigrations(MIGRATION_1_2) // ADD MIGRATION
.build()
...//

If you had more than one migration, you have a comma and continue adding them in the function addMigrations()

That’s it. Easy does it.

Further reading on migrations can be found on the resources below:

  1. https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.Builder
  2. https://developer.android.com/training/data-storage/room/migrating-db-versions

--

--

Henry "Dru" Onyango

Building products somewhere in Africa. Sometimes I write.