Room Database Auto Migration
Tags: room, android, androiddev
Published: 2024-03-19 17:44:45

### Introduction
Building offline-first Apps to enhance user experience during limited network connectivity is a common practice among developers. The Room Persistence Library, a part of the Android Architecture components, facilitates this by enabling local data storage. As the application scales, developers often encounter the need to fine-tune existing features or introduce new ones. When deploying these changes to end-users, it's crucial to preserve their existing on-device data. However, manual adjustments to the database schema can become cumbersome as the application evolves. This is where auto migration comes into play, automating the process and streamlining development workflows.

### Setting up auto migration
To set up auto migration with Room, you'll first need to configure the Gradle Plugin to export the schema to a designated directory. In your project's top-level `build.gradle` file (`/build.gradle.kts`), declare the Room plugin and its version:
```Kotlin
plugins {
id androidx.room' version '2.6.0' apply false
}
```
Then, in the app-level `build.gradle` file (`/app/build.gradle.kts`), apply the plugin and specify the directory where the schema will be exported:
```Kotlin
plugins {
id 'androidx.room'
}
room {
schemaDirectory "$projectDir/schemas"
}
```
Ensure that the schema storage directory is located outside of your app directory. The `$projectDir` variable references the root directory of your project, and `/schemas` denotes the directory where the database file will be stored.

Next, set the `exportSchema` property to `true` within the `@Database()` annotation class. This step is essential as Room relies on the generated schema to execute migrations:
```Kotlin
@Database( 
version = SampleDatabaseClass.LATEST_VERSION
entities = [Entity::class]
exportSchema = true
)
abstract class SampleDatabaseClass : RoomDatabase() {
companion object {
const val LATEST_VERSION = 1
}
...
}
```
After completing these steps, rebuild your project to export the current database schema history into a JSON file.

### Performing auto migration
To declare an automated migration between two database versions, specify the existing version and the new version. For instance, if the previous version was `1`, and after making changes, the version is updated to `2`, you can declare this migration in the database class:
```Kotlin
@Database( 
version = SampleDatabaseClass.LATEST_VERSION,
entities = [Entity::class],
exportSchema = true,
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
abstract class SampleDatabaseClass : RoomDatabase() {
companion object {
const val LATEST_VERSION = 2
}
...
}
```
Rebuild your project to let Room handle the migration seamlessly. If further changes are made in subsequent versions, such as migrating from version `2` to `3`, simply add the corresponding migration.

When you move to version `3` we will have to migrate from `2` to `3`. We will simply add this to the class:

```Kotlin
@Database( 
version = SampleDatabaseClass.LATEST_VERSION,
entities = [Entity::class],
exportSchema = true,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3)
]
)
abstract class SampleDatabaseClass : RoomDatabase() {
companion object {
const val LATEST_VERSION = 3
}
...
}
```

### Auto Migration Specifications
In some cases, Room may require additional information to perform migrations, particularly when changes involve deleting or renaming tables or columns. For such scenarios, you can use `AutoMigrationSpec` to provide the necessary details.

For example, if you need to rename a table, you can define an `AutoMigrationSpec` class within your database class and annotate it accordingly:

```Kotlin
@Database( 
version = SampleDatabaseClass.LATEST_VERSION,
entities = [Entity::class],
exportSchema = true,
autoMigrations = [
AutoMigration (from = 1,
 to = 2, 
 spec = SampleDatabaseClass.YourAutoMigration::class),

]
)
abstract class SampleDatabaseClass : RoomDatabase() {
companion object {
const val LATEST_VERSION = 3
}

@RenameTable(fromTableName = "old_name", toTableName = "new_name")
class SampleAutoMigration : AutoMigrationSpec { }
...
}
```

### Conclusion
The implementation of auto migration in Room significantly simplifies the process of managing database schema changes, enhancing the overall developer experience. By automating migration tasks, developers can focus more on building and refining app features without the burden of manual data management.

### References
- https://medium.com/androiddevelopers/room-auto-migrations-d5370b0ca6eb
- https://developer.android.com/training/data-storage/room/migrating-db-versions
- https://developer.android.com/reference/kotlin/androidx/room/AutoMigration

### See it in action
- https://github.com/slowburn-404/NoteWave