Christophe Blin
Christophe Blin

Reputation: 1909

Intercepting room migration errors to recreate the DB

There is a great article about room migrations at https://medium.com/google-developers/understanding-migrations-with-room-f01e04b07929

However, I still miss a "disaster" recovery mechanism in Room (i.e like fallbackToDestructiveRecreationOnMigrationError() that would clear and recreate the db when something strange happens)

Here is what happened to us :

A dev pushed version 9 with a migration, but the schema 9.json was not in accordance (how is that possible I dont know) => room did the migration and no error.

Then, when we provided version 10, file 9.json has changed => room crashed and users were not able to access the app anymore (which is kind of normal).

We had to tell our users to wipe their data (which is not a technical problem since we resync the db on app start, but this is a public relation problem)

I think it should be possible with openHelperFactory in the builder, but this is far to deep into room internals for me :(

Upvotes: 3

Views: 872

Answers (1)

Christophe Blin
Christophe Blin

Reputation: 1909

After many tries and desperation, here is the solution I've used :

abstract class MainDatabase: RoomDatabase() {
    companion object {
        val instance: MainDatabase by lazy {
            if (_instance == null) throw IllegalStateException("someone should have called init fun")
            _instance!!
        }

        private var _instance: MainDatabase? = null
        fun init(mainApplication: MainApplication) {
            _instance = init_(mainApplication)
            //force db opening and if it fails, we try to destroy and recreate the db
            try {
                _instance!!.openHelper.writableDatabase
            } catch (e: Exception) {
                Log.e("Database", "there was an error during DB opening => trying to destroy and recreate", e)
                _instance!!.openHelper.close()
                val dbPath = mainApplication.getDatabasePath(DB_NAME)
                if (SQLiteDatabase.deleteDatabase(dbPath)) {
                    _instance = init_(mainApplication)
                    _instance!!.openHelper.writableDatabase
                }
            }
        }

        private fun init_(mainApplication: MainApplication): MainDatabase {
            return Room.databaseBuilder(mainApplication, MainDatabase::class.java, DB_NAME)
                    .addMigrations(MIGRATION_1, MIGRATION_2, MIGRATION_3, MIGRATION_4, MIGRATION_5, MIGRATION_6, MIGRATION_7, MIGRATION_8, MIGRATION_9, MIGRATION_10)
                    .build()
        }
    }

The real downside of this solution is that the first access to the db is done on the main thread ...

If anyone has a better solution, please share !

Upvotes: 5

Related Questions