From f5b0e7f15e30e5b783a0f83a63098ae849108eb0 Mon Sep 17 00:00:00 2001 From: Serchinastico <54cymru@gmail.com> Date: Tue, 29 Jan 2019 19:12:02 +0100 Subject: [PATCH] Add a migration to version 2 to include the whole model into de DB Add tests Fix migration --- .../2.json | 58 ++++++++++++++ .../data/repository/room/MigrationTest.kt | 76 +++++++++++++++++++ .../superheroes/SuperHeroesApplication.kt | 8 +- .../superheroes/common/SuperHeroesDatabase.kt | 53 ++++++++++++- .../repository/LocalSuperHeroDataSource.kt | 8 +- .../data/repository/room/SuperHeroDao.kt | 8 +- .../data/repository/room/SuperHeroEntity.kt | 11 +-- 7 files changed, 200 insertions(+), 22 deletions(-) create mode 100644 app/schemas/com.karumi.jetpack.superheroes.common.SuperHeroesDatabase/2.json create mode 100644 app/src/androidTest/java/com/karumi/jetpack/superheroes/data/repository/room/MigrationTest.kt diff --git a/app/schemas/com.karumi.jetpack.superheroes.common.SuperHeroesDatabase/2.json b/app/schemas/com.karumi.jetpack.superheroes.common.SuperHeroesDatabase/2.json new file mode 100644 index 0000000..4f87eff --- /dev/null +++ b/app/schemas/com.karumi.jetpack.superheroes.common.SuperHeroesDatabase/2.json @@ -0,0 +1,58 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "4250c22060f75f31b611cfdc08f9177d", + "entities": [ + { + "tableName": "superheroes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`superhero_id` TEXT NOT NULL, `superhero_name` TEXT NOT NULL, `superhero_photo` TEXT, `superhero_isAvenger` INTEGER NOT NULL, `superhero_description` TEXT NOT NULL, PRIMARY KEY(`superhero_id`))", + "fields": [ + { + "fieldPath": "superHero.id", + "columnName": "superhero_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "superHero.name", + "columnName": "superhero_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "superHero.photo", + "columnName": "superhero_photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "superHero.isAvenger", + "columnName": "superhero_isAvenger", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "superHero.description", + "columnName": "superhero_description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "superhero_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"4250c22060f75f31b611cfdc08f9177d\")" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/karumi/jetpack/superheroes/data/repository/room/MigrationTest.kt b/app/src/androidTest/java/com/karumi/jetpack/superheroes/data/repository/room/MigrationTest.kt new file mode 100644 index 0000000..9afbe0a --- /dev/null +++ b/app/src/androidTest/java/com/karumi/jetpack/superheroes/data/repository/room/MigrationTest.kt @@ -0,0 +1,76 @@ +package com.karumi.jetpack.superheroes.data.repository.room + +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.platform.app.InstrumentationRegistry +import com.karumi.jetpack.superheroes.common.Migrations +import com.karumi.jetpack.superheroes.common.SuperHeroesDatabase +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +class MigrationTest { + companion object { + private const val TEST_DB = "migration-test" + private const val SUPER_HERO_ID = "IronMan" + private const val SUPER_HERO_NAME = "Iron Man" + private val SUPER_HERO_URL: String? = null + private const val SUPER_HERO_IS_AVENGER = true + private const val SUPER_HER_DESCRIPTION = "Iron Man is a super hero" + } + + @Rule + @JvmField + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + SuperHeroesDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory() + ) + + @Test + fun migrate1To2() = withDb( + fromVersion = 1, + toVersion = 2, + given = { insertSuperHeroInVersion1() }, + then = { assertSuperHeroExistsInVersion2(this) } + ) + + private fun withDb( + fromVersion: Int, + toVersion: Int, + given: SupportSQLiteDatabase.() -> Unit, + then: SupportSQLiteDatabase.() -> Unit + ) { + val db = helper.createDatabase(TEST_DB, fromVersion) + given(db) + helper.runMigrationsAndValidate(TEST_DB, toVersion, true, *Migrations.all) + then(db) + helper.closeWhenFinished(db) + } + + private fun assertSuperHeroExistsInVersion2(db: SupportSQLiteDatabase) { + val cursor = db.query("SELECT * FROM superheroes") + cursor.moveToFirst() + assertEquals(SUPER_HERO_ID, cursor.getString(0)) + assertEquals(SUPER_HERO_NAME, cursor.getString(1)) + assertEquals(SUPER_HERO_URL, cursor.getString(2)) + assertEquals(SUPER_HERO_IS_AVENGER.toInt(), cursor.getInt(3)) + assertEquals(SUPER_HER_DESCRIPTION, cursor.getString(4)) + } + + private fun SupportSQLiteDatabase.insertSuperHeroInVersion1() { + execSQL( + """ + INSERT INTO superheroes VALUES( + "$SUPER_HERO_ID", + "$SUPER_HERO_NAME", + $SUPER_HERO_URL, + ${SUPER_HERO_IS_AVENGER.toInt()}, + "$SUPER_HER_DESCRIPTION" + )""" + ) + } +} + +private fun Boolean.toInt() = if (this) 1 else 0 diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/SuperHeroesApplication.kt b/app/src/main/java/com/karumi/jetpack/superheroes/SuperHeroesApplication.kt index 558920d..9114039 100644 --- a/app/src/main/java/com/karumi/jetpack/superheroes/SuperHeroesApplication.kt +++ b/app/src/main/java/com/karumi/jetpack/superheroes/SuperHeroesApplication.kt @@ -1,7 +1,6 @@ package com.karumi.jetpack.superheroes import android.app.Application -import androidx.room.Room import com.karumi.jetpack.superheroes.common.SuperHeroesDatabase import com.karumi.jetpack.superheroes.common.module import com.karumi.jetpack.superheroes.data.repository.LocalSuperHeroDataSource @@ -34,12 +33,7 @@ class SuperHeroesApplication : Application(), KodeinAware { private fun appDependencies(): Kodein.Module = module { bind() with singleton { - Room.databaseBuilder( - this@SuperHeroesApplication, - SuperHeroesDatabase::class.java, - "superheroes-db" - ).fallbackToDestructiveMigration() - .build() + SuperHeroesDatabase.build(this@SuperHeroesApplication) } bind() with provider { val database: SuperHeroesDatabase = instance() diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/common/SuperHeroesDatabase.kt b/app/src/main/java/com/karumi/jetpack/superheroes/common/SuperHeroesDatabase.kt index bb596dc..cc5fa74 100644 --- a/app/src/main/java/com/karumi/jetpack/superheroes/common/SuperHeroesDatabase.kt +++ b/app/src/main/java/com/karumi/jetpack/superheroes/common/SuperHeroesDatabase.kt @@ -1,11 +1,62 @@ package com.karumi.jetpack.superheroes.common +import android.content.Context import androidx.room.Database +import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroDao import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroEntity -@Database(entities = [SuperHeroEntity::class], version = 1) +@Database(entities = [SuperHeroEntity::class], version = SuperHeroesDatabase.version) abstract class SuperHeroesDatabase : RoomDatabase() { abstract fun superHeroesDao(): SuperHeroDao + + companion object { + const val version = 2 + fun build(context: Context): SuperHeroesDatabase = + Room.databaseBuilder(context, SuperHeroesDatabase::class.java, "superheroes-db") + .addMigrations(*Migrations.all) + .build() + } +} + +object Migrations { + val from1To2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE `superheroes_temp` ( + `superhero_id` TEXT NOT NULL, + `superhero_name` TEXT NOT NULL, + `superhero_photo` TEXT, + `superhero_isAvenger` INTEGER NOT NULL DEFAULT 0, + `superhero_description` TEXT NOT NULL, + PRIMARY KEY(`superhero_id`) + ) + """ + ) + database.execSQL( + """ + INSERT INTO `superheroes_temp`( + `superhero_id`, + `superhero_name`, + `superhero_photo`, + `superhero_isAvenger`, + `superhero_description` + ) SELECT + `id`, + `name`, + `photo`, + `isAvenger`, + `description` + FROM `superheroes`""" + ) + database.execSQL("DROP TABLE superheroes") + database.execSQL("ALTER TABLE `superheroes_temp` RENAME TO `superheroes`") + } + } + + val all = arrayOf(from1To2) } \ No newline at end of file diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt index 41239fa..413fd04 100644 --- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt +++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/LocalSuperHeroDataSource.kt @@ -23,14 +23,12 @@ class LocalSuperHeroDataSource( fun save(superHero: SuperHero): SuperHero { executor.execute { - dao.insertAll(listOf(superHero.toEntity())) + dao.update(superHero.toEntity()) } return superHero } - private fun SuperHeroEntity.toSuperHero(): SuperHero = - SuperHero(id, name, photo, isAvenger, description) + private fun SuperHeroEntity.toSuperHero(): SuperHero = superHero - private fun SuperHero.toEntity(): SuperHeroEntity = - SuperHeroEntity(id, name, photo, isAvenger, description) + private fun SuperHero.toEntity(): SuperHeroEntity = SuperHeroEntity(this) } \ No newline at end of file diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt index efa3d9d..03dc2c1 100644 --- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt +++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroDao.kt @@ -4,18 +4,22 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Update @Dao interface SuperHeroDao { - @Query("SELECT * FROM superheroes ORDER BY id ASC") + @Query("SELECT * FROM superheroes ORDER BY superhero_id ASC") fun getAll(): List - @Query("SELECT * FROM superheroes WHERE id = :id") + @Query("SELECT * FROM superheroes WHERE superhero_id = :id") fun getById(id: String): SuperHeroEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(superHeroes: List) + @Update + fun update(superHero: SuperHeroEntity) + @Query("DELETE FROM superheroes") fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroEntity.kt b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroEntity.kt index 2d098e2..854251a 100644 --- a/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroEntity.kt +++ b/app/src/main/java/com/karumi/jetpack/superheroes/data/repository/room/SuperHeroEntity.kt @@ -1,13 +1,10 @@ package com.karumi.jetpack.superheroes.data.repository.room +import androidx.room.Embedded import androidx.room.Entity -import androidx.room.PrimaryKey +import com.karumi.jetpack.superheroes.domain.model.SuperHero -@Entity(tableName = "superheroes") +@Entity(tableName = "superheroes", primaryKeys = ["superhero_id"]) data class SuperHeroEntity( - @PrimaryKey val id: String, - val name: String, - val photo: String?, - val isAvenger: Boolean, - val description: String + @Embedded(prefix = "superhero_") val superHero: SuperHero ) \ No newline at end of file