1

I am trying to use Kotlin to:
- prepopulate the room database in my app from an existing SQL database.
- allow the user to update the data.
- let the updated data persist for the current version only.
- have the new version flush out the old data and replace it with the newly shipped SQL database.

Here's what I did so far:
I used createFromAsset method to prepopulate the database with fallbackToDestructiveMigration() call as follows:

@Database(entities = [MCData::class], version = 1, exportSchema = false)
abstract class MyRoom : RoomDatabase() {
    abstract val myDao: MyDao

    companion object {

        @Volatile 
        private var INSTANCE: MyRoom? = null

        fun getInstance(context: Context): MyRoom {

            synchronized(this) {

                var instance = INSTANCE

                if (instance == null) {

                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        MyRoom::class.java,
                        "mcdata.db")
                        .createFromAsset("mydata.db")
                        .fallbackToDestructiveMigration()
                        .build()
                    INSTANCE = instance
                }

                return instance
            }
        }
    }

This is prepopulating the data and letting the user change it, but the updates in the data after the initial population are getting lost when the app restarts. I'm puzzled by this behavior, because it looks like the code is using the fallbackToDestructiveMigration() to refresh the data even though the schema is not changed (the users can only change the values of two existing columns).

If I remove the fallbackToDestructiveMigration() method, then the user's updates after initial pre-population persist, but when I reinstall the app with the new data, the database does not get updated.

Is there a way to have the data (including edits) persist in the current version, but gets replaced by the new pre-population SQL database when the new version is installed?

SMISA16
  • 11
  • 3
  • Your desired behavior is really strange. I would expect the user to be rather unhappy when you delete their added data. If you are sure that this is the behavior that you want, you will probably need to detect the new app version yourself (Room knows nothing about that) and delete the database files before trying to open the database. – CommonsWare Apr 11 '20 at 17:59
  • CommonsWare: Thanks for the quick response. The user does not add significant data, he can only edit lock unlock type flags, which he can reset with the new app version. Could you elaborate on how I can delete the database files? I am new to this and still trying to learn... – SMISA16 Apr 11 '20 at 18:09
  • You can call `getDatabasePath()` on a `Context` to get the path to your database file. – CommonsWare Apr 11 '20 at 18:11
  • CommonsWare: Thanks for the tip. I will try it, but what I don't understand is why the database is getting refreshed back to the pre-populate database, when the app restarts, even though it has the same database schema. – SMISA16 Apr 11 '20 at 19:36
  • It's possible that `createFromAsset()` and `fallbackToDestructiveMigration()` are not meant to be used in combination. Personally, I have not tried that. – CommonsWare Apr 11 '20 at 19:39
  • It is given as an example here: https://developer.android.com/training/data-storage/room/prepopulate – SMISA16 Apr 11 '20 at 19:43
  • If the schema in your pre-packaged database does not match the one from your code, `fallbackToDestructiveMigration()` might blow away the previously-unpacked database, then unpack it again. – CommonsWare Apr 11 '20 at 19:56

1 Answers1

0

I ended up creating two builder functions:
- The first one is called when the software is installed for the first time or updated, which reads from the asset database.
- The second one is called at other times, which reads from a locally created database, so the users' small tweaks persist for the version.

@Database(entities = [MCData::class,FBData::class,WhereAmIdata::class], version = 1, exportSchema = false)
abstract class MyRoom : RoomDatabase() {
    abstract val myDao: MyDao

    companion object {

        @Volatile // with volatile, changes immediately become visible to all threads
        private var INSTANCE: MyRoom? = null

        // this instantiaion is called the first time the software is updated
        fun getInstanceAfterSoftwareUpdate(context: Context): MyRoom {

            synchronized(this) {
                var instance = INSTANCE

                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        MyRoom::class.java,
                        "mcdata")
                        .createFromAsset("mydata.db")
                        .fallbackToDestructiveMigration()
                        .build()

                    INSTANCE = instance
                }
                return instance
            }
        }


        fun getInstance(context: Context): MyRoom {

            synchronized(this) {

                var instance = INSTANCE

                if (instance == null) {

                    Log.i("MyRoom", "reading locally")
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        MyRoom:: class.java,
                        "mcdata"
                    )
                        .fallbackToDestructiveMigration()
                        .build()

                    INSTANCE = instance
                }

                return instance
            }
        }
    }
}
SMISA16
  • 11
  • 3