Problem - Room DB getting wiped/cleared when doing force update play store update. I am working on a chat messenger application which uses Room DB as local database. Whenever I do a store update with increasing DB version, the local DB gets cleared and messages history are lost.
I'm Using Room DB. My Application is in the Play Store with the use of Room DB and the version is 4.
My Question is I'm changing the 9 tables schema, and now that I update the DB version, each table schema changes. Should I increase the DB version here? How can I accomplish this without losing the user data using Room DB for force update in Play Store? Ex. DB version is 4, I change the two tables’ elements like in the below query.
Do I need to increase DB version twice as two tables are changed or change to one number incremental will be fine? Example: Do I need DB to increase version to 6 OR keeping it 5 is enough?
private val mMigrationMessageStatus: Migration = object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE message_status RENAME TO MessageStatus")
database.execSQL("ALTER TABLE MessageStatus ADD COLUMN userId TEXT NOT NULL default ''")
}
}
private val mMigrationGroupMember: Migration = object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE group_member RENAME TO GroupMember")
database.execSQL("ALTER TABLE GroupMember ADD COLUMN userId TEXT NOT NULL default ''")
}
}
return Room.databaseBuilder(context, AppDatabase::class.java, dbName)
.allowMainThreadQueries()
.addMigrations(mMigrationMessageStatus,mMigrationGroupMember)
.build()
From room version 2.4.0, you can easily update using autoMigrations.
DATABASE CLASS
#Database(
version = 3,
autoMigrations = [
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3)
],
.....
)
DATA CLASS
#Entity(tableName = "user")
data class DataUser(
....
// I added this column, like this
#ColumnInfo(defaultValue = "")var test: String = ""
)
see reference below
android developer: room version2.4.0
android developer: autoMigration
Related
I had this data class:
data class User(
val name:String,
val age: Int
)
I save users' data with this in my database, and now the problem is I want to add a new field
val gender: String
and if I added this the app crashed when retrieving users' data from the database because it is not the same data any more.
This is my code to retrieve data from the database:
#Query("SELECT*FROM users WHERE name =:name")
suspend fun getUser(name:String):Flow<User>
I would be most interested in seeing a pseudocode solution if possible.
You need to do a migration of your database. You can find all the instructions to do it there Migrating Room databases.
If your room version is 2.4.0-alpha01 or higher:
Replace in your database:
#Database(
version = 1,
entities = {User.class}
)
with:
#Database(
version = 2,
entities = {User.class},
autoMigrations = {
#AutoMigration (from = 1, to = 2)
}
)
Else you will need to du the migration manually:
Add this to your Database class:
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE users "
+ " ADD COLUMN gender TEXT");
}
};
and add .addMigrations(MIGRATION_1_2) to your Room.databaseBuilder()
You have two solutions, the right one and the fast one:
The right one:
#Database(
version = 2,
entities = [User::class],
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
Code taken from: https://developer.android.com/training/data-storage/room/migrating-db-versions#automated
This is a migration , since in this particular case you are only adding a column to a table you can try this approach and it will most likely work. If it doesn't then you have to implement your own migration with some SQL code.
The fast one:
Wherever you are creating your data base, add the following method: .fallbackToDestructiveMigration() . This will make that whenever your database version changes and a migration is needed and not provided it will just delete the old database and recreate it. Take into account that this solution will wipe out all the info you had saved up until that point, so try to use it only if you are in a rush and don't care to maintain the data between updates.
When updading DB is it acceptable to run large code to align the DB to my requirements.
For example, I need to alter the table and change column names. Then I need to get all my data in the DB and check if file is located than update the DB accordingly. I need it happen only once when user updates the app to this Room version.
val MIGRATION_8_9 = object : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE RideEntity RENAME videoPresent TO videoState")
GlobalScope.launch(Dispatchers.IO) {
val rides = DataBaseHelper.getAllPartsFromDB() //get all data
rides.forEach {
val path = MyApp.appContext.getExternalFilesDir(null)!!.path + "/" + it.name + "/"
val file = File(path + VIDEO_FILE).exists()
if (file) {
it.videoState = 1
DataBaseHelper.updateData(it) //set the data
}
}
}
}
}
Where:
suspend fun getAllPartsFromDB() = withContext(Dispatchers.IO) {
val parts = db.rideDao().getAllParts()
parts
}
Function:
#Query("SELECT * FROM rideentity ORDER BY time DESC")
fun getAllParts(): List<Parts>
So my question, despite this works, is this way acceptable? And if the migrate function called only once when the app DB updated from version X to Y
Is it acceptable to manage large DB manipulations inside Room migration?
Yes. However you may wish to put the update loop inside a transaction.
And if the migrate function called only once when the app DB updated from version X to Y
Yes it is only called the one time. The Migration(8,9) determines this that is the Migration will only be invoked when the version, as stored in the database header, is 8 and then the version number is set to 9.
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()
I'm new to android room library. I need to migrate a Not Null column to Null,
But room migration only allow ADD or RENAME in ALTER table query. How do execute a column migration query?
#Entity(tableName = "vehicle_detail")
data class VehicleDetailsEntity(
#PrimaryKey(autoGenerate = true)
val vehicleClientId: Long = 0,
val vehicleId: String,
val updatedOn: Date,
val updatedBy: String
)
I need to change table structure into
#Entity(tableName = "vehicle_detail")
data class VehicleDetailsEntity(
#PrimaryKey(autoGenerate = true)
val vehicleClientId: Long = 0,
val vehicleId: String,
val updatedOn: Date?,
val updatedBy: String?
)
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.
You need to run a migration since SQLite doesn't allow column constraint modification.
For that migration you need to create a new temp table and copy all your previous data to it, then delete the old table and rename the temp one to the needed table name.
If you have a scheme directory, you can find your exact creation SQL query which you should copy on your migration (I just figured it out from a scheme of mine and could not be 100% correct):
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// Create the new table
database.execSQL(
"CREATE TABLE IF NOT EXISTS VehicleDetailsEntityTmp (vehicleId TEXT NOT NULL, updatedOn TEXT, updatedBy TEXT,vehicleClientId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL )"
)
// Copy the data
database.execSQL(
"INSERT INTO VehicleDetailsEntityTmp (vehicleId, updatedOn, updatedBy ,vehicleClientId) SELECT vehicleId, updatedOn, updatedBy ,vehicleClientId FROM VehicleDetailsEntity ")
// Remove the old table
database.execSQL("DROP TABLE VehicleDetailsEntity")
// Change the table name to the correct one
database.execSQL("ALTER TABLE VehicleDetailsEntityTmp RENAME TO VehicleDetailsEntity")
}
}
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()