The solution proposed by the previous post is simply remove
"fallbackToDestructiveMigration" but this is not a solution just a
workaround since If I update my Database and my Database version, the
app will throw an exception since no migration were provided.
Removing fallbackToDestructiveMigration
will solve the problem of rewriting/recreating the database on each request.
But now you won't be able to do a database migration in the next release because you don't specify a migration plan with addMigrations()
, otherwise you'll get IllegalStateException
.
Trying to catch that exception will fail as Room
won't allow that, something like:
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
TestDatabase database;
try {
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.allowMainThreadQueries()
.build();
} catch (IllegalStateException e) {
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
}
return database;
}
So, as you don't have migration plans with addMigrations()
, the only way is to manipulate using the fallbackToDestructiveMigration
by some flag, so we call fallbackToDestructiveMigration
conditionally upon the need.
A possible solution is to control that with two integers, one indicates the current database version, and the other indicates the new database version. Either SharedPrefs
or DataStore
are typical tools for that:
Assuming the current database version is 1, and in the new version is 2:
Create SharedPrefs
helper methods:
static String SHARED_PREFS_NAME = "SHARED_PREFS_NAME";
static String CURRENT_DB_VERSION = "CURRENT_DB_VERSION";
static String NEW_DB_VERSION = "NEW_DB_VERSION";
public static int getCurrentDBVersion(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
return prefs.getInt(CURRENT_DB_VERSION , 1);
}
public static void setCurrentDBVersion(Context context, int version) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(CURRENT_DB_VERSION , version);
editor.apply();
}
public static int getNewDBVersion(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
return prefs.getInt(NEW_DB_VERSION, 1);
}
public static void setNewDBVersion(Context context, int version) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(NEW_DB_VERSION, version);
editor.apply();
}
Then check the conditional in the Database class:
@Database(entities = {...},
version = 2)
public abstract class TestDatabase extends RoomDatabase {
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
TestDatabase database;
int newDBVersion = getNewDBVersion(context);
int currentDBVersion = getNewDBVersion(context);
if (newDBVersion == currentDBVersion) { // No migration required
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.allowMainThreadQueries()
.build();
} else { // Migration is needed (call fallbackToDestructiveMigration)
database = Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
// set the current version to the new version as the migration is done.
setCurrentDBVersion(context, newDBVersion);
}
return database;
}
}
And whenever you have a release that requires a database migration, you have to set the new database version to the new version, it'd be in the activity onCreate()
callback:
setNewDBVersion(this, 2);
Make sure to set that before accessing the database.
If there is no migration required in some release, keep the setNewDBVersion(this, 1)
with the current release.
Another Approach for conditional calling of the fallbackToDestructiveMigration()
Instead of maintaining database versions in some storage (SharedPrefs/DataStore), we would check the database version:
Quote from this answer
A changed (increased) USER_VERSION can be detected by just accessing
the files (the current database and the asset) and reading the first
100 bytes and then extracting the 4 bytes at offset 60 (68 for the
Application Id).
And that answer referenced the below method for that purpose:
private static boolean isNewAsset(Context context, String asset, String dbname) {
File current_Db = context.getDatabasePath(dbname);
if(!current_Db.exists()) return false; /* No Database then nothing to do */
int current_Db_version = getDBVersion(current_Db);
Log.d("DBINFO","isNewAsset has determined that the current database version is " + current_Db_version);
if (current_Db_version < 0) return false; /* No valid version */
int asssetVersion = getAssetVersion(context,asset);
Log.d("DBINFO","isNewAsset has determined that the asset version is " + asssetVersion);
if (asssetVersion > current_Db_version) {
Log.d("DBINFO","isNewAsset has found that the asset version is greater than the current db version " + current_Db_version);
return true;
} else {
Log.d("DBINFO","isNewAsset has found that the asset version is unchanged " + current_Db_version);
}
return false;
}
Side Notes:
allowMainThreadQueries()
should not be used in a production app, just can be used for testing and simplicity purpose.
Personally, I'd prefer to add database migrations instead of using fallbackToDestructiveMigration()
even for prepopulated databases; probably as the later is destructive :).
If there are no schema (tables/columns) changes; i.e., just need to update the database entries (rows); we can just have an empty migration plan, and just delete the database on any upgrade with deleteDatabase()
on Context to refresh the database in the assets:
@Database(entities = {...},
version = 2)
public abstract class TestDatabase extends RoomDatabase {
@Provides
public TestDatabase provideDatabase(@ApplicationContext Context context) {
int newDBVersion = getNewDBVersion(context);
int currentDBVersion = getNewDBVersion(context);
if (newDBVersion != currentDBVersion) { // Migration required
// delete the database to refresh it on the assets and remove any cached version
context.deleteDatabase("databases/test.db");
// set the current version to the new version as the migration is done.
setCurrentDBVersion(context, newDBVersion);
}
return Room.databaseBuilder(
context.getApplicationContext(),
TestDatabase.class,
DATABASE_NAME)
.createFromAsset("databases/test.db")
.allowMainThreadQueries()
.addMigrations(migration_v1_v2)
.build();
}
// Empty Migration plan
static Migration migration_v1_v2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Log.d("TAG","Empty Migration plan for data insertion, deletion, or update - Migration from V1 to V2");
}
};
}