I am wondering how to rename or delete a Room Database which was instantiated by following code:
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
Should I expect a database-name.db file in getApplicationContext().getFilesDir(), which I could then rename or delete? Would this approach be safe?
My current workaround would be to use only one database with multiple tables, emulating multiple databases. I am not sure if this approach scales very well though.
I could not find any answers to this question in the documention. I am not asking how to rename or delete columns or rows.
To rename a room database you can do the following when getting your database instance :
//Kotlin
fun getDatabase(context: Context) : YourRoomDbClass {
return INSTANCE ?: synchronized(this){
val dbFile = context.applicationContext.getDatabasePath("your_old_db_name.db")
if(dbFile.exists()) dbFile.renameTo(File(dbFile.path + "your_new_db_name.db"))
val instance = Room.databaseBuilder(context.applicationContext, YourRoomDbClass::class.java, "your_new_db_name.db")
.build()
INSTANCE = instance
instance
}
}
Related
I have 2 db files in asset. I have to prepopulate 2 tables
#Database(entities = [Quotes::class, Anime::class], version = 1, exportSchema = true)
Here I have tried something but it isn't working
#Provides
#Singleton
fun provideDatabase(
app: Application,
)= Room.databaseBuilder(app, QuotesDatabase::class.java, "quotes_database")
.createFromAsset("quotess.db")
.createFromAsset("animes.db")
.fallbackToDestructiveMigration()
.build()
You have a few options.
The simplest way would be to combine the two into a single asset. This could be done using one of the SQLite tools (SQliteStudio, DBeaver, Navicat for SQLite, DB Browser for SQLite).
You could, without using createFromAsset allow Room to build the database and the open each asset in turn and copy the data in the onCreate callback. When the onCreate callback is invoked, the database has been created along with the tables and it is passed to the callback as a SupportSQLiteDatabase. You could then copy each asset to a suitable location (databases directory), open the asset as an SQLiteDatabase, for each table, extract the data into a Cursor and then load the data from the Cursor into the SupportSQliteDatabase. You can the close the two SQLiteDatabase and then delete them.
A third option, would be to, prior to building the Room database, create the database (according to the SQL that can be found in the generated java), copying the two asset to a suitable location (databases directory) attaching both to the created database, copying the data from both to the respective tables detach the two asset databases and delete them. Then when building the Room database it will exist and be opened.
I don't believe that you need to, but you may have to set the user version of the created database.
Here's an in-principle (untested) example of the second option that you may find useful.
:-
#Database(entities = [Quotes::class,Anime::class], exportSchema = false, version = 1)
abstract class QuotesDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
#Volatile
private var instance: QuotesDatabase?=null
private var quotesAssetFileName = "quotess.db" /* CHANGE AS REQUIRED */
private var animeAssetFileName = "animes.db" /* CHANGE AS REQUIRED */
private var quotesTableName = "quotes_table" /* CHANGE AS REQUIRED */
private var animeTablename = "anime_table" /* CHANGE AS REQUIRED */
fun getInstance(context: Context): QuotesDatabase {
if (instance==null) {
instance = Room.databaseBuilder(context,QuotesDatabase::class.java,"quotes_database")
.addCallback(cb)
.build()
}
return instance as QuotesDatabase
}
val cb = object: Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
db.beginTransaction() /* MAY NOT BE ABLE TO BE USED - IF NOT REMOVE (as well as similar below)*/
copyAsset(Application().applicationContext, quotesAssetFileName)
val asset1db = SQLiteDatabase.openDatabase(Application().getDatabasePath(quotesAssetFileName).path,null,0)
populateFromCursor(asset1db.rawQuery("SELECT * FROM $quotesTableName",null), quotesTableName,db)
copyAsset(Application().applicationContext, animeAssetFileName)
val asset2db = SQLiteDatabase.openDatabase(Application().getDatabasePath(animeAssetFileName).path,null,0)
populateFromCursor(asset2db.rawQuery("SELECT * FROM $animeTablename",null), animeTablename,db)
db.setTransactionSuccessful() /* MAY NOT BE ABLE TO BE USED - IF NOT REMOVE (as well as similar below)*/
db.endTransaction() /* MAY NOT BE ABLE TO BE USED - IF NOT REMOVE (as well as similar below)*/
deleteAssetCopy(asset1db)
deleteAssetCopy(asset2db)
}
}
/* Populates the Room database using the extracted data (Cursor) from the asset copy database */
#SuppressLint("Range")
fun populateFromCursor(csr: Cursor, tableName: String, db: SupportSQLiteDatabase) {
val cv = ContentValues()
while (csr.moveToNext()) {
for (c in csr.columnNames) {
cv.put(c,"'" + csr.getString(csr.getColumnIndex(c))+"'")
}
db.insert(tableName,OnConflictStrategy.IGNORE,cv)
}
csr.close()
}
/* Copies the asset to the asset database */
fun copyAsset(context: Context, assetFileName: String) {
val asset = context.assets.open(assetFileName)
val db = context.getDatabasePath(assetFileName)
val os: OutputStream = db.outputStream()
asset.copyTo(os)
}
/* Deletes the copied assets database */
fun deleteAssetCopy(db: SQLiteDatabase) {
File(db.path).delete()
}
}
}
I have tried many ways to reset it.
allowBackupandfullBackupOnly had been set to false.
.fallbackToDestructiveMigration()
and delete database and cache files directly.
but it doesn't work.
Simplest way is to uninstall the app, this deletes the database file(s). So rerunning starts from a brand new database.
To use .fallbackToDestructiveMigration() you have to have to invoke a Migration by increasing the version number but NOT have a Migration for the particular path. You could argue that this doesn't reset the database as the newly created database will have the higher version number.
Using clearAllTables doesn't entirely reset the database as it will not delete the system tables. Most notably, sqlite_sequence, which is a table that hold the vale of the latest rowid on a per table basis. That is if you have autogenerate = true in the #PrimaryKey annotation for an field/column that resolves to a column type affinity of INTEGER then AUTOINCREMENT is coded then the sqlite_sequence table will be created (if not already in existence) and store the latest (and therefore highest) value of the said primary key. Thus if you have inserted 100 rows (for example) into a such a table, then after a clearAllTables the 100 will still be stored in the sqlite_sequnce table.
You could also, prior to building the database, delete the database. Here's an example that allows it to be deleted when building :-
#Database(entities = [Customer::class], version = 1)
abstract class CustomerDatabase: RoomDatabase() {
abstract fun customerDao(): CustomerDao
companion object {
private var instance: CustomerDatabase?=null
fun getDatabase(context: Context, resetDatabase: Boolean): CustomerDatabase {
if (resetDatabase && instance == null) {
(context.getDatabasePath("thedatabase.db")).delete()
}
if (instance == null) {
instance = Room.databaseBuilder(context,CustomerDatabase::class.java,"thedatabase.db")
.allowMainThreadQueries()
.build()
}
return instance as CustomerDatabase
}
fun getDatabase(context: Context): CustomerDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,CustomerDatabase::class.java,"thedatabase.db")
.allowMainThreadQueries()
.build()
}
return instance as CustomerDatabase
}
}
}
note that in addition to requesting the reset, a check is also made to ensure that an instance of the database hasn't been retrieved.
This would also be more efficient as clearAllTables still incurs processing of the underlying data and the ensuing VACUUM which can be quite resource hungry.
You can use this clear all tables
THis deletes all rows from all the tables that are registered to this database as Database.entities().
The Room database has a clearAllTables function that does clearing entities you defined with #Entity annotation. But there is a catch. It does not clear the system generated tables such as sqlite_sequence, which stores the autoincrement values.
But there are more factors to consider. Since clearAllTables itself run in a transaction, we cannot run combination of clearAllTables and clearing sqlite_sequence in a single transaction. If you try to run clearAllTables in a transaction, it will fail with an IllgalStateException.
The android SQLite database library creates an additional table called android_metadata which stores database locale, and the room database library creates another table called room_master_table, which keeps track of database integrity and helps database migrations. We should not delete or clear these two tables. Additionally, SQLite will create a sqlite_sequence table if you have defined autoincrement columns. Deleting this table is not allowed, but clearing this will reset the autoincrement values.
The room database compiler generates the clearAllTables function in the database class. Basically, it disables foreign key constraints, then starts a transaction and clears all rows in tables you have given in the database class, and after the end of the transaction, it re-enables foreign key constraints. See how this is done in the room database compiler source code room / room-compiler / src / main / kotlin / androidx / room / writer / DatabaseWriter.kt / createClearAllTables. The generated function differs based on one factor, whether you have defined foreign key constraints or not.
Based on the compiler source code, I wrote an extension function to reset the database. It will clear all tables you defined and will reset the autoincrement values.
fun RoomDatabase.resetDatabase(tables: List<String>? = null): Boolean {
val db = openHelper.writableDatabase
val tableNames = db.getTableNames()
val hasForeignKeys = db.hasForeignKeys(tables ?: tableNames.minus("sqlite_sequence"))
val supportsDeferForeignKeys = db.supportsDeferForeignKeys()
return try {
if (hasForeignKeys && !supportsDeferForeignKeys) {
// clear enforcement of foreign key constraints.
db.execSQL("PRAGMA foreign_keys = FALSE")
}
db.beginTransaction()
if (hasForeignKeys && supportsDeferForeignKeys) {
// enforce foreign key constraints after outermost transaction is committed.
db.execSQL("PRAGMA defer_foreign_keys = TRUE")
}
// clear all tables including sqlite_sequence table.
// deleting sqlite_sequence table is required to reset autoincrement value.
val tablesToClear = tables?.let {
if (tableNames.contains("sqlite_sequence")) {
it.plus("sqlite_sequence")
} else {
it
}
} ?: tableNames
for (tableName in tablesToClear) {
db.execSQL("DELETE FROM $tableName")
}
db.setTransactionSuccessful()
true
} catch (e: Exception) {
false
} finally {
db.endTransaction()
if (hasForeignKeys && !supportsDeferForeignKeys) {
// restore enforcement of foreign key constraints.
db.execSQL("PRAGMA foreign_keys = TRUE")
}
// blocks until there is no database writer and all are reading from the most recent database snapshot.
db.query("PRAGMA wal_checkpoint(FULL)").close()
if (!db.inTransaction()) {
db.execSQL("VACUUM")
}
}
}
fun SupportSQLiteDatabase.getTableNames(
exclude: List<String> = listOf("android_metadata", "room_master_table")
): List<String> {
val cursor = query("SELECT DISTINCT tbl_name FROM sqlite_master WHERE type='table'")
val tables = mutableListOf<String>()
while (cursor.moveToNext()) {
tables.add(cursor.getString(0))
}
cursor.close()
tables.removeAll(exclude)
return tables
}
fun SupportSQLiteDatabase.hasForeignKeys(tables: List<String>? = null): Boolean {
val tableNames = tables ?: getTableNames(exclude = listOf("android_metadata", "room_master_table", "sqlite_sequence"))
for (tableName in tableNames) {
val cursor = query("PRAGMA foreign_key_list($tableName)")
if (cursor.count > 0) {
cursor.close()
return true
}
cursor.close()
}
return false
}
fun SupportSQLiteDatabase.supportsDeferForeignKeys(): Boolean {
// defer_foreign_keys is only supported on API 21+
// Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
val cursor = query("PRAGMA defer_foreign_keys")
return cursor.use { it.count > 0 }
}
I am using Room with a prepopulated database in the assets folder. For an app update, I would like to alter this database by adding a new column and prepopulating this column with new data.
The database was auto-migrated from version 1 to 2 (a table was added). From version 2 to 3, I would now like to apply abovementioned changes by providing a different 'database.db' file in the assets folder and allowing for destructive migration.
#Database(entities = [Object1::class, Object2::class], version = 3, autoMigrations = [
AutoMigration (from = 1, to = 2)], exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): Dao
companion object {
private const val DB_NAME = "database.db"
#Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java, "AppDB.db")
.fallbackToDestructiveMigration()
.createFromAsset(DB_NAME)
.build()
}
}
}
The problem is that I still get the following exception:
java.lang.IllegalStateException: A migration from 1 to 3 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
I am unsure why this would still happen. I thought it was either providing a migration script or allowing for destructive migration that makes the migration work.
Added Comment:-
I have tried an implemented migration, but the same exception as above happened again. When I try starting over with versionCode 1, I am getting "java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number." I have also changed the database name and added android:allowBackup="false" in the manifest.
Any ideas?
I had problems using fallbackToDestructiveMigration and createFromAsset together. I would like to share my experience because it took me hours to find it. When you provide an asset db, you have to update the user version pragma of the default database file that you are providing with createFromAsset. If not, you always lose the data that you insert while the app is working.
I finally figured out what the problem was, it had nothing to do with the versioning or anything else related to room or the asset db file.
It was dependency injection.
I provided my database to Dagger in a DatabaseModule class as follows:
private const val DB_NAME = "database.db"
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Provides
fun provideDao(appDatabase: AppDatabase): Dao {
return appDatabase.dao()
}
#Provides
#Singleton
fun provideAppDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java, "AppDB.db")
.createFromAsset(DB_NAME)
.build()
}
}
It was missing the fallBackToDestructiveMigration() call, so this messed up Room's internal onUpgrade call in RoomOpenHelper.java.
To fix it, I made my buildDatabase call in AppDatabase public and used it to provide the database to Dagger in the DatabaseModule class.
Digging through the room documentation doesn't turn much up, my hunch is that it has to do with the fact that you are using Automigrations instead of implemented migrations. Have you tried changing that Automigration from 1->2 to an implemented migration?
Also, since you are manually replacing it with a new database that has prepopulated data my solution would be to just get rid of the old migrations, change the name of the DB slightly and start over from version 1. There's no reason to maintain the old migrations if anyone going from older versions to the current version are having their DB deleted.
After extensive methodical testing, the only way that I can replicate your (1-3 required) failure is by excluding fallbackToDestructiveMigation. In which case the exception happens if the migration is from 1 to 3 or the migration is 3 to 1 (i.e. Asset Version at 3 but Room version at 1)
as per the spreadsheet screenshot below
1-3 exception when AssetDB Version =3 Database Version = 1 Room Version = 3
also 3-1 exception when AssetDB Version =3 Database Version = -1 Room Version = 1
-1 version means file does not exist (i.e. initial install)
I suspect that you have somehow inadvertently introduced one of the above two scanrios. What I haven't tested is alternative Room library versions. The above was tested with 2.4.0-alpha04 as per :-
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.room:room-ktx:2.4.0-alpha04'
implementation 'androidx.room:room-runtime:2.4.0-alpha04'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
kapt 'androidx.room:room-compiler:2.4.0-alpha0
For the testing, I had two copies of the asset file, one at version 1 the other at version 2 (v1dbbase.db and v3dbbase.db), the data in a common column indicating the if the data was for version3. The actual asset file used was deleted before a test and the appropriate version copied and pasted to database.db
I had the two entities Object1 and Object2 and could comment in or out an extra column in either. e.g.:-
/*TESTING INCLUDE FOR V2+ >>>>>*///, #ColumnInfo(name = COL_EXTRAV2, defaultValue = "x") val object1_extra: String
- as above it is excluded
/*TESTING INCLUDE FOR V2+ >>>>>*/, #ColumnInfo(name = COL_EXTRAV2, defaultValue = "x") val object1_extra: String
- with the two //'s before the comma now included
both the extra columns commented out = Version 1
Object1's extra column included = Version 3
Object1's and Object2's extra column included = Version 3
Object2's extra column included but not Object1's was not considered.
A few constants were added to cater for logging.
Additionally to cater for logging a callback function was added (.addCallback) and onOpen, onCreate and onDestructiveMigration were all overridden to log the Room Version and Database Version.
To further enhance the logging, two functions were added, to get the version from the sqlite database header. One for the asset file, the other for the database. The functions being called/invoked BEFORE the database build.
To run a test it meant:-
Ensuring that the device had the App at the appropriate level.
Deleting the database.db asset
Copying and pasting the appropriate asset file as database.db (from either v1dbbase.db or v3dbbase.db)
Amending the Object1 class to include/exclude the extra column (as explained above)
Amending the Object2 class to include/exclude the extra columns (as explained above)
Amended the Room Version to the appropriate level.
The code used for testing:-
Object1
#Entity(tableName = TABLE_NAME)
data class Object1(
#PrimaryKey
#ColumnInfo(name = COL_ID)
val object1_id: Long,
#ColumnInfo(name = COL_NAME)
val object1_name: String
/*TESTING INCLUDE FOR V2+ >>>>>*///, #ColumnInfo(name = COL_EXTRAV2, defaultValue = "x") val object1_extra: String
) {
companion object {
const val TABLE_NAME = "object1"
const val COL_ID = TABLE_NAME + "_object1_id"
const val COL_NAME = TABLE_NAME + "_object1_name"
const val COL_EXTRAV2 = TABLE_NAME + "_object1_extrav2"
}
}
Object2
#Entity(tableName = TABLE_NAME)
data class Object2(
#PrimaryKey
#ColumnInfo(name = COL_ID)
val object2_id: Long,
#ColumnInfo(name = COL_NAME)
val object2_name: String
/*TESTING INCLUDE FOR V3>>>>>*///, #ColumnInfo(name = COL_EXTRAV3, defaultValue = "x") val object3_extrav3: String
) {
companion object {
const val TABLE_NAME = "object2"
const val COL_ID = TABLE_NAME + "_object2_id"
const val COL_NAME = TABLE_NAME + "_object2_name"
const val COL_EXTRAV3 = TABLE_NAME + "_object2_extrav3"
}
}
Dao
#Dao
abstract class Dao {
#Insert
abstract fun insert(object1: Object1): Long
#Insert
abstract fun insert(object2: Object2): Long
#Query("SELECT * FROM ${Object1.TABLE_NAME}")
abstract fun getAllFromObject1(): List<Object1>
#Query("SELECT * FROM ${Object2.TABLE_NAME}")
abstract fun getAllFromObject2(): List<Object2>
}
AppDatabase
#Database(
entities = [Object1::class, Object2::class],
version = AppDatabase.DBVERSION,
autoMigrations = [AutoMigration (from = 1, to = 2)],
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): Dao
companion object {
private const val DB_NAME = "database.db"
private const val DB_FILENAME = "AppDB.db" //<<<<< ADDED for getting header
const val TAG = "DBINFO" //<<<< ADDED for logging
const val DBVERSION = 1 //<<<<<ADDED for logging
#Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
//ADDED>>>>> to get database version from dbfile and assets before building the database
Log.d(TAG,
"AssetDB Version =${getAssetDBVersion(context, DB_NAME)} " +
"Database Version = ${getDBVersion(context, DB_FILENAME)} " +
"Room Version = ${DBVERSION}")
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java, DB_FILENAME)
.fallbackToDestructiveMigration()
.createFromAsset(DB_NAME)
.allowMainThreadQueries()
.addCallback(rdc)
.build()
}
/* Call Backs for discovery */
object rdc: RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d(TAG,"onCreate called. DB Version = ${db.version}, Room Version is ${DBVERSION}")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d(TAG,"onOpen called. DB Version = ${db.version}, Room Version is ${DBVERSION}")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d(TAG,"onDestructiveMigration called. DB Version = ${db.version}, Room Version is ${DBVERSION}")
}
}
fun getAssetDBVersion(context: Context, assetFilePath: String): Int {
var assetFileHeader = ByteArray(100)
try {
var assetFileStream = context.assets.open(assetFilePath)
assetFileStream.read(assetFileHeader,0,100)
assetFileStream.close()
} catch (e: IOException) {
return -2 // Indicates file not found (no asset)
}
return ByteBuffer.wrap(assetFileHeader,60,4).getInt()
}
fun getDBVersion(context: Context, dbFileName: String): Int {
var SQLiteHeader = ByteArray(100)
val dbFile = context.getDatabasePath(dbFileName)
if(dbFile.exists()) {
var inputStream = dbFile.inputStream()
inputStream.read(SQLiteHeader, 0, 100)
inputStream.close()
return ByteBuffer.wrap(SQLiteHeader, 60, 4).getInt()
} else {
return -1 // Indicates no database file (e.g. new install)
}
}
}
}
you may wish to consider including the logging above, it could very easily detect issues with the version(s) being used.
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: AppDatabase
lateinit var dao: Dao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = AppDatabase.getInstance(this)
dao = db.dao()
for(o1: Object1 in dao.getAllFromObject1()) {
logObject1(o1)
}
for(o2: Object2 in dao.getAllFromObject2()) {
logObject2(o2)
}
}
fun logObject1(object1: Object1) {
Log.d(TAG,"ID is ${object1.object1_id}, Name is ${object1.object1_name}")
}
fun logObject2(object2: Object2) {
Log.d(TAG,"ID is ${object2.object2_id}, Name is ${object2.object2_name}")
}
companion object {
const val TAG = AppDatabase.TAG
}
}
In addition to utilising the above code and ensuring that the 6 tasks were undertaken I also kept a spreadsheet of the versions and the results e.g. :-
Previous answer (not the case after testing)
I believe that your issue may be with the pre-populated database, in that it's version number (user_version) hasn't been changed to 3.
you can change the version using the SQL (from an SQlite tool ) PRAGMA user_version = 3;
The documentation says :-
Here is what happens in this situation:
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.
Because there is no implemented migration plan from version 2 to version 3, the migration is a fallback migration.
Because the fallbackToDestructiveMigration() builder method is called, the fallback migration is destructive. Room drops the database instance that's installed on the device.
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.
By note perhaps by the way of an exception?
I'm using Room and I need to perform a database migration. I've migrated the data but I have a problem in one of the columns. When the migration is performed, the data for that column may still be unavailable.
When the user enters the data needed for that column, I have to get all rows that match a value in that column, update these values by the one provided by the user and drop all other rows that do not match.
I can have a method in my UserDao but the problem is that this does not seem correct because it's a one time only thing and I don't what to expose the method so my idea was to get the database instance and try to do the change myself.
When I use
var myDatabase = Room.databaseBuilder(....)
.addMigrations(... .build()
I keep a reference to it but then, when I do myDatabase.openHelper.writableDatabase I'm always getting an exception
getDatabase called recursively
Any idea how to handle this?
Your issue is that you are trying to use the MyDatabase's openHelper to try to get the database when building the instance of MyDatabase which is in the process of getting the database, so while getting the database you are then trying to get the database.
Instead you need to use the SupportSQLiteDatabase that is passed to the Migration.
As ean example :-
#Database(
version = 1,
entities = [
MyTableEntity::class
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun MyTableEntityDao(): MyTableEntityDao
companion object {
val MIGRATION_V1_V2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
//........ code using the already opened database
database.execSQL(????????); //<<<<<<<<<< USES the support database
}
}
}
}
This would then be invoked using something similar to :-
var myDatabase = Room.databaseBuilder(applicationContext,AppDatabase::class.java,"mydatabase")
.allowMainThreadQueries()
.addMigrations(AppDatabase.MIGRATION_V1_V2)
.build()
Im not clear on how to use room after i have updated the database version.
For example, lets say i originally had the following database defined in room:
#Database(entities = {Event.class}, version = 1)
#TypeConverters(DateTypeConverter.class)
public abstract class EventDatabase extends RoomDatabase {
public abstract EventDao eventDao();
}
and then i change the version so that it looks like this now:
#Database(entities = {Event.class}, version = 2)
#TypeConverters(DateTypeConverter.class)
public abstract class EventDatabase extends RoomDatabase {
public abstract EventDao eventDao();
}
when i saw change the version i mean that i may have added or deleted columns in the database so its not same. my questions are the following:
do i need to maintain two databases now ? v1 and v2 ? and is there a way to copy entities easily over to v2 ? also when changing the version is it enough to simply change it from 1 to 2 or do i have to create another class called EventDatabase2 for example ?
also here is the version of room i am using:android.arch.persistence.room:runtime:1.0.0-alpha1
So lets say i have a new app version and a new database version also. I simply need to change the version = 2 like this:
#Database(entities = {Event.class}, version = 2)
#TypeConverters(DateTypeConverter.class)
public abstract class EventDatabase extends RoomDatabase {
public abstract EventDao eventDao();
}
and then provide a migration policy like this:
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))");
}
};
the key thing here is if a migration policy is not provided it seem the entire database is rebuilt (so your user would loose all previous data).
this is according to #commonsWare update link provided .
My answer may be late, but it may help someone like me who has only recently found the answer to the same question.
For some reason you need to upgrade the database version but do not need to adjust the database, as simple as editing the #Dao adapter or the #Entity attribute without affecting the structure of the database.
If you upgrade the Version in the Database as below:
From:
#Database(
entities = [ExampleClass::class],
version = 1,
exportSchema = false
)
To:
#Database(
entities = [ExampleClass::class],
version = 2,
exportSchema = false
)
If you do not add anything later, the database will be refreshed as if it were deleted. To avoid deletion, you can simply add an empty Migration as follows:
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
}
}
build your database:
#Synchronized
private fun buildDatabase(context: Context, databaseName: String): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
databaseName
)
.addMigrations(MIGRATION_1_2)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
}
Database data will not be affected
Another option:
You can update the database version number after a modification in your DB schema, and at the same time you can use fallbackToDestructiveMigration() method in the construction of your database object if you don't want to provide a migration plan and just apply the changes you want, although it's always recommended to provide a migration trace:
#Provides
#Singleton
fun provideCryptocurrenciesDatabase(app: Application): Database = Room.databaseBuilder(app,
Database::class.java, "my_db").fallbackToDestructiveMigration().build()