Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

02b-room-migrations #8

Open
wants to merge 1 commit into
base: 02a-room
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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\")"
]
}
}
Original file line number Diff line number Diff line change
@@ -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() }
)

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 SupportSQLiteDatabase.assertSuperHeroExistsInVersion2() {
val cursor = 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
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -35,12 +34,7 @@ class SuperHeroesApplication : Application(), KodeinAware {

private fun appDependencies(): Kodein.Module = module {
bind<SuperHeroesDatabase>() with singleton {
Room.databaseBuilder(
this@SuperHeroesApplication,
SuperHeroesDatabase::class.java,
"superheroes-db"
).fallbackToDestructiveMigration()
.build()
SuperHeroesDatabase.build(this@SuperHeroesApplication)
}
bind<SuperHeroDao>() with provider {
val database: SuperHeroesDatabase = instance()
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ class LocalSuperHeroDataSource(
}

fun save(superHero: SuperHero): SuperHero {
executor.execute { dao.insertAll(listOf(superHero.toEntity())) }
executor.execute { 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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<SuperHeroEntity>

@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<SuperHeroEntity>)

@Update
fun update(superHero: SuperHeroEntity)

@Query("DELETE FROM superheroes")
fun deleteAll()
}
Original file line number Diff line number Diff line change
@@ -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
)