Adding a Suspect Property

Next, open Crime.kt and give Crime a property to hold the name of a suspect. Provide a default value – an empty string – so that you do not have to update places in your codebase where you create a Crime.

Listing 16.3  Adding a suspect property (Crime.kt)

@Entity
data class Crime(
    @PrimaryKey val id: UUID,
    val title: String,
    val date: Date,
    val isSolved: Boolean,
    val suspect: String = ""
)

Since you updated your Crime class, and Room uses that class to create database tables for you, you need to make some changes to your database as well. Specifically, you need to increment the version of your CrimeDatabase class and tell Room how to migrate your database between the versions.

Room uses a versioning system to manage how data is structured within a database. Databases are intended as long-term storage for data, but as your app grows and adds new features, your model classes – and, by extension, your database – might change. You might need to add a new property to one of your entities, as you just did, which would cause a new column to be added to your database. Or you might need to change the type of one of your entity’s properties, or perhaps remove a property altogether.

In all these cases, Room needs to know how to manage the change so that the structure of your database stays in sync with your database entity classes.

Room tracks the version of your database with the version property inside the @Database annotation on your CrimeDatabase class. When you first created the CrimeDatabase class, you set that value to 1. As you make changes to the structure of your database, such as adding the suspect property on the Crime class, you increment that value. Since your initial database version is set to 1, you need to bump it up to 2 now.

When your app launches and Room builds the database, it will first check the version of the existing database on the device. If this version does not match the one you define in the @Database annotation, Room will begin the process to migrate that database to the latest version.

Room offers you two ways to handle migrations. The easy way is to call the fallbackToDestructiveMigration() function when building your CrimeDatabase instance. But, as the name hints, when this function is invoked Room will delete all the data within the database and re-create a new version. This means that all the data will be lost, leading to very unhappy users.

The better way to handle migrations is to define Migration classes. The Migration class constructor takes in two parameters. The first is the database version you are migrating from, and the second is the version you are migrating to. In this case, you will provide the version numbers 1 and 2.

The only function you need to implement in your Migration object is migrate(SupportSQLiteDatabase). You use the database parameter to execute any SQL commands necessary to upgrade your tables. (Room uses SQLite under the hood, as you read about in Chapter 12.) The ALTER TABLE and ADD COLUMN commands will add the new suspect column to the crime table.

Open CrimeDatabase.kt, increment the version, and add a migration. Between versions 1 and 2 of CriminalIntent’s database, a new property was added to Crime: a String property named suspect. The corresponding migration will include a single instruction to add a suspect column to the table that stores your crimes.

Listing 16.4  Adding database migration (database/CrimeDatabase.kt)

@Database(entities = [Crime::class], version = 1 version = 2)
@TypeConverters(CrimeTypeConverters::class)
abstract class CrimeDatabase : RoomDatabase() {
    abstract fun crimeDao(): CrimeDao
}

val migration_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(
            "ALTER TABLE Crime ADD COLUMN suspect TEXT NOT NULL DEFAULT ''"
        )
    }
}

After you create your Migration, you need to provide it to your database when it is created. Open CrimeRepository.kt and provide the migration to Room when creating your CrimeDatabase instance. Call addMigrations(…) before calling the build() function. addMigrations() takes in a variable number of Migration objects, so you can pass all your migrations in when you declare them.

Listing 16.5  Providing migration to Room (CrimeRepository.kt)

class CrimeRepository private constructor(
    context: Context,
    private val coroutineScope: CoroutineScope = GlobalScope
) {

    private val database: CrimeDatabase = Room
        .databaseBuilder(
            context.applicationContext,
            CrimeDatabase::class.java,
            DATABASE_NAME
        )
        .addMigrations(migration_1_2)
        .build()
    ...
}

Once your migration is in place, run CriminalIntent to make sure everything builds correctly. The app behavior should be the same as before you applied the migration, and you should see the crime you added in Chapter 15. You will make use of the newly added column shortly.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset