Search code examples
androidsqliteandroid-roomdatabase-migrationillegalstateexception

Room don't work migration when I try to add new column in table


After adding new migration I have the next exception. What didn't I do? When I debuging, migration was done. The new column is text and I try to write all data in one cell.

java.lang.IllegalStateException: Pre-packaged database has an invalid schema: library(com.tnco.runar.model.LibraryItemsModel).
 Expected:
TableInfo{name='library', columns={audio_duration=Column{name='audio_duration', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, rune_tags=Column{name='rune_tags', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, image_url=Column{name='image_url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, link_url=Column{name='link_url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, link_title=Column{name='link_title', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, audio_url=Column{name='audio_url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, title=Column{name='title', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, type=Column{name='type', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, childs=Column{name='childs', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, sort_order=Column{name='sort_order', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, content=Column{name='content', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
 Found:
TableInfo{name='library', columns={audio_duration=Column{name='audio_duration', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, image_url=Column{name='image_url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, link_url=Column{name='link_url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, link_title=Column{name='link_title', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, audio_url=Column{name='audio_url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, title=Column{name='title', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, type=Column{name='type', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, childs=Column{name='childs', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, sort_order=Column{name='sort_order', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, content=Column{name='content', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}

Expected: rune_tags=Column BUT didn't found

My DataBase class:

@Database(entities = [LibraryItemsModel::class, ...],
version = 4,
exportSchema = false)
abstract class AppDB : RoomDatabase() {
abstract fun appDAO(): AppDAO

companion object {
    @Volatile
    private lateinit var INSTANCE: AppDB
    fun init(context: Context) {

        var dataBaseFilePath = "database/layouts.db"
       
        INSTANCE = Room.databaseBuilder(context, AppDB::class.java, "EN_DATABASE")
            .createFromAsset(dataBaseFilePath)
            .addMigrations(MIGRATION_2_3, MIGRATION_3_4)
            .build()
    }

    fun getLayoutDB(): AppDB {
        return INSTANCE
    }


    private val MIGRATION_2_3 = object : Migration(2, 3) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("CREATE TABLE runes_generator (id INTEGER PRIMARY KEY NOT NULL, imgUrl TEXT, enTitle TEXT, ruTitle TEXT)")
        }
    }


    private val MIGRATION_3_4 = object : Migration(3, 4) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("ALTER TABLE library ADD COLUMN rune_tags TEXT")
        }
    }

Model class:

@Entity(tableName = "library")
@TypeConverters(LibraryConverter::class)
data class LibraryItemsModel(
@PrimaryKey
var id: String,
var childs: List<String>?,
var content: String?,
var title: String?,
@ColumnInfo(name = "image_url")
var imageUrl: String?,
@ColumnInfo(name = "sort_order")
var sortOrder: Int?,
var type: String?,
@ColumnInfo(name = "link_title")
var linkTitle: String?,
@ColumnInfo(name = "link_url")
var linkUrl: String?,
@ColumnInfo(name = "audio_url")
var audioUrl: String?,
@ColumnInfo(name = "audio_duration")
var audioDuration: Int?,
@ColumnInfo(name = "rune_tags")
var runeTags: List<String>?
) {
 constructor() : this(
    "null",
    listOf(),
    null,
    null,
    null,
    null,
    null,
    null,
    null,
    null,
    null,
    listOf()
)
}

I added @ColumnInfo(name = "rune_tags") var runeTags: List?

Also I have Converter class

class LibraryConverter {

@TypeConverter
fun fromList(items: List<String>?): String? {
    return items?.stream()?.collect(Collectors.joining("|"))
}

@TypeConverter
fun toList(data: String?): List<String> {
    return data?.split("|") ?: listOf()
}

}


Solution

  • Your issue is that the pre-packaged database (the asset) does not include the schema change (and if I recall correctly is not at version 3) AND that the database doesn't exist on the device (so the database is being copied from the asset/prepackaged database).

    Thus the pre-packaged database is being copied, it's version is then set to, or already at version 4 so no migration is undertaken and thus the resultant message:-

    Pre-packaged database ....

    To cater for new installs you need/should to have the pre-packaged database using the current schema.

    Or you could use the PrePackagedDatabaseCallback to alter the table. You may find How do I use Room's prepackagedDatabaseCallback? useful.

    • It is suggested that you changed the pre-packaged database rather than use the call-back, as you are then always moving forward through a single change.
      • if you had a pre-packaged database always at the original state. Then you would have to ensure that the logic of the current change considered previous changes rather than just considered the most recent change.
        • thus, if following the progressive prepackaged database principle, the changes to the pre-packaged data could test most, if not all, of the proposed migration.
        • If not following the principle then it may be that the migration and the call-back process differ and neither would be as complete a test of the other.