0

I have an app with multiple migrations, one of them is from a version previous to the use of Room to a version when Room was introduced. I have found that it runs with RoomOpenHelper.onUpgrade callback, but when I use SafeHelperFactory it creates an instance of SQLiteOpenHelper which itself holds a callback to RoomOpenHelper. I think it is creating conflicts in the migration path. The strange thing is that this issue is not happening when I migrate from a version of the app that already has Room (with no encryption) to a version with SafeRoom, it also runs smoothly when migrating from no Room to Room (with no encryption). The stacktrace I'm getting is the following (although not sure how much context on the migration will be needed, I don't think the migration itself is the issue as it works when migrating from no Room to Room and has been working for a while, the issue arises when I'm trying to introduce SafeRoom, please look at the order of the function/method calls being made).

Process: com.xx.xx.debug, PID: 16780
    net.sqlcipher.database.SQLiteException: error in view guest_summary: no such table: main.my_guests: ALTER TABLE my_guests_temp RENAME TO my_guests
        at net.sqlcipher.database.SQLiteDatabase.native_execSQL(Native Method)
        at net.sqlcipher.database.SQLiteDatabase.execSQL(SQLiteDatabase.java:2417)
        at com.commonsware.cwac.saferoom.Database.execSQL(Database.java:371)
        at MyDatabaseKt$MIGRATION_15_16$1.migrate(MyDatabase.kt:460)
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:87)
        at com.commonsware.cwac.saferoom.Helper$OpenHelper.onUpgrade(Helper.java:207)
        at net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:182)
        at com.commonsware.cwac.saferoom.Helper$OpenHelper.getWritableSupportDatabase(Helper.java:172)
        at com.commonsware.cwac.saferoom.Helper.getWritableDatabase(Helper.java:82)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:256)
        at androidx.room.util.DBUtil.query(DBUtil.java:54)

The version with no room is 15. I use Dagger so the way I integrate SafeRoom is as follows.

    /**
     * Provide the single instance of our room database
     */
    @Singleton
    @Provides
    fun provideDatabase(@Named("app") context: Context): MyDatabase {
        val databaseState = SQLCipherUtils.getDatabaseState(context, "DatabaseName.db")
        Log.d("ROOM", "database status: $databaseState")
        if (databaseState == SQLCipherUtils.State.UNENCRYPTED) {
            Log.d("ROOM", "start encryption")
            val passphrase = "databasekey".toCharArray()
            SQLCipherUtils.encrypt(context, "DatabaseName.db", passphrase)
            Log.d("ROOM", "finish encryption")
        }

        val factory = SafeHelperFactory.fromUser(SpannableStringBuilder("databasekey"))

        return Room
                .databaseBuilder(
                        app,
                        MyDatabase::class.java,
                        "DatabaseName.db"
                )
                .openHelperFactory(factory)
                .allowMainThreadQueries()
                .addMigrations(*getMigrationList())
                .build()
    }

If more context is needed I can provide. If this is a known issue or already has a workable solution please redirect me. Thanks.

  • You really do not want to run `encrypt()` on the main application thread, and right now you are not controlling what thread that is run on. Plus, your encryption is using a hardcoded passphrase, which is pointless. If that is what your real code is doing, just remove SafeRoom. – CommonsWare Nov 06 '19 at 17:42
  • @CommonsWare yes, I'm aware of the hardcoded passphrase, this is part of a spike to check if SafeRoom can be added to the app and it would work to decide its integration. About the `encrypt()` call, where would be the best place to use it in a context of Room being provided via Dagger? – Esteban Alarcón Ceballos Nov 06 '19 at 17:52
  • I don't think that it is necessarily practical to have DI for the database in this scenario. The repository that wraps around the database could use DI, but the repository cannot open the database until it gets the passphrase, which requires user interaction. Similarly, the repository should not be encrypting the database without that passphrase, at which point the repository can arrange to perform that encryption on a background thread. Rolling all the way back to your problem, you would need to examine the encrypted DB and see what tables it has. – CommonsWare Nov 06 '19 at 17:55

0 Answers0