Migration to insert new rows of data into Room DB

Viewed 1250

I'm making a new release of my app with new functionality that requires more rows of data in a Settings table in the Room DB. Even though structurally my DB has not changed (no new tables, no column changes etc) I was thinking of running a new migration (DB v2 -> v3) on the Room DB just to add these new rows of data to an existing table. Is that overkill?

DB_INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            MyDatabase.class, Constants.DB_NAME)
                            .addMigrations(new Migration_1_2(context, 1, 2), new Migration_2_3(2, 3))
                            .build();

I know I can add data in an onCreate() / onOpen() callback in my RoomDatabase. E.g.

private static RoomDatabase.Callback rdc = new RoomDatabase.Callback() {
            public void onCreate (SupportSQLiteDatabase db) {
                // do something after database has been created
            }
            public void onOpen (SupportSQLiteDatabase db) {
                // do something every time database is open
            }
        };

But I'm not sure either of these are appropriate?

onCreate() - for existing app users this won't be called as they'll already have the DB from the previous app release.

onOpen() - would run every single time they start the app - which seems a lot of overhead.

At least with a new migration this would only run once for users. Is this the right method?

4 Answers

I am sure by now you must have found a solution, I will just leave this here for anyone having the same issue.

The simple answer is yes, you're on the right track. Another option would be to create a boolean preference and assign the opposite value after the user has updated the app and new rows have been inserted. Meaning you would have to write code to access the database, perform the CRUD operation and also edit the shared preference value.

Use the migration method to avoid writing too much code if you plan on inserting a few rows else use the second method. Here is a sample from one of my side projects.

static final Migration MIGRATION_4_5 = new Migration(4, 5) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {


        database.execSQL("INSERT INTO categories (id,type, name, amount, description) "
                + "SELECT NULL, 'expense', 'food', 0, NULL "
                + "UNION ALL SELECT NULL, 'expense', 'transportation', 0, NULL ");

    }
};

I added a callback to my db creation to make sure any user installing the current version also has the new data

      private static AppDatabase buildDatabase(final Context appContext,
                                         final AppExecutors executors) {
    return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
            .addCallback(new Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);
                    executors.diskIO().execute(new Runnable() {
                        @Override
                        public void run() {

                            // Generate the data for pre-population
                            AppDatabase database = AppDatabase.getInstance(appContext, executors);

                            List<Category> categories = DataGenerator.generateExpenseCategories();

                           insertData(database, categories);


                        }
                    });
                }
            })

       .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)

            .build();
}

Try this to add migration.

Add column name inside Entity

 @ColumnInfo(name = "columnName")
 private String columnName;

Use this block of code for executing migration query

 static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
              database.execSQL("ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + type + " DEFAULT NULL");
        }
    };

Add migration

.addMigrations(MIGRATION_1_2,MIGRATION_2_3)
.build();

then increase your db version.

The data for the database can be "prepopulated" with data from a file.

// Destructive migrations are enabled and a prepackaged database is provided.
DB_INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            MyDatabase.class, Constants.DB_NAME)
                            .createFromFile(new File("mypath"))
                            .addMigrations(new Migration_1_2(context, 1, 2))
                            .fallbackToDestructiveMigration()
                            .build();

Alternatively you can load the data from an asset:

.createFromAsset("database/myapp.db")

This is explained here:

https://developer.android.com/training/data-storage/room/prepopulate

  1. Because the database defined in your app is on version 3 and the database instance already installed on the device is on version 2, a migration is necessary.
  2. Because there is no implemented migration plan from version 2 to version 3, the migration is a fallback migration.
  3. Because the fallbackToDestructiveMigration() builder method is called, the fallback migration is destructive. Room drops the database instance that's installed on the device.
  4. Because there is a prepackaged database file that is on version 3, Room recreates the database and populates it using the contents of the prepackaged database file. If, on the other hand, you prepackaged database file were on version 2, then Room would note that it does not match the target version and would not use it as part of the fallback migration.

You can insert a new row to an existing table through migration, like this:

val MIGRATION_2_3 = object : Migration(2, 3) {
    
        override fun migrate(database: SupportSQLiteDatabase) {
            database.insert(
                "SomeTable",
                CONFLICT_REPLACE,
                ContentValues(2).apply {
                    put("column_name", "some value")
                    put("column_name_1", "some_value_1")
                })
        }
}

Just insert a new row into the existing table when you migrate

Related