Room Database schema update without data loss - android

I have developed one application in Android using Kotlin and it is available on playstore. I have used Room database to store the values. I have following queries:
Database schema is changed now, How to I need to handle that. I referred to below tutorial but still not clear to handle the schema change in Migration.
Visit https://developer.android.com/training/data-storage/room/migrating-db-versions.html
How can I test my current application with playstore version?
Thanks for the help in advance.

That's quite a complex question but basically you have 2 strategies:
fallbackToDestructiveMigration -> simple to implement but your users will lose their data once the app is updated
Provide a Migrationstrategy (preferable)
Case 1 - fallbackToDestructiveMigration
In your database initialization, simply invoke fallbackToDestructiveMigration on your database builder:
database = Room.databaseBuilder(context.getApplicationContext(),
UsersDatabase.class, "Sample.db")
.fallbackToDestructiveMigration()
.build();
In this case, since you have updated your database version (suppose from version 1 to version 2) Room can't find any migration strategy, so it will fallback to distructive migration, tables are dropped.
Case 2 - Smart migration
Suppose you have a table called "Users" and suppose you added a column to this table in version 2 of your database. Let's call this column "user_score" You should implement migrate interface of Migration class in order to update your "Users version 1" schema to "Users version 2" schema. To do so, you need an alter table, you can write it directly inside the migrate method:
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// Your migration strategy here
database.execSQL("ALTER TABLE Users ADD COLUMN user_score INTEGER")
}
};
database = Room.databaseBuilder(context.getApplicationContext(),
UsersDatabase.class, "Sample.db")
.addMigrations(MIGRATION_1_2)
.build();
More references here :
https://medium.com/androiddevelopers/understanding-migrations-with-room-f01e04b07929
https://medium.com/#elye.project/android-sqlite-database-migration-b9ad47811d34

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

For your question 2, you can do the following things:
Download and install apk file in mobile device from playstore.
Build your apk file(signed apk). Before generating the apk file
don't forget to increase version code.
install the signed apk file in device by following below adb command
adb install -r "apk_file_path"
Hope this will work.

You should use a VCS (like git). For each version of your app you should create a tag, so you can easily switch between published versions to test compatibility.
The migration itself can be done by invoking raw queries on the room database in your migration. If you don't know how to write those, you should first read some tutorials about SQL.

Related

createFromAsset and fallbackToDestructiveMigration methods of Room library - no data exists

I have a .db file in assets folder. My RoomDatabase class is as the following. I install the app to my device. Then I changed version = 2 in the below class, and make my prepopulated database version 2. Then i renamed one of the columns in my table so that schema is changed. Then i installed the app again. And boom! Room gives me the following error :
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.
#Database(entities = [Word::class], version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(ctx: Context): WordRoomDatabase {
val passphrase: ByteArray = SQLiteDatabase.getBytes("my password".toCharArray())
val factory = SupportFactory(passphrase)
return Room.databaseBuilder(ctx.applicationContext, WordRoomDatabase::class.java, "word_database2.db")
.openHelperFactory(factory)
.createFromAsset("word_database.db")
.fallbackToDestructiveMigration()
.build()
}
}
}
After this point, I completely deleted my app. Make the prepopulated database version and #Database version 1. I made allowBackup false in AndroidManifest.xml. So there is no database exist in my device. But I am still facing the same error when I install the app to device. What a nonsense error is this. How can I solve it ?
I faced the same issue in the near past. I'm quoting from http://www.sqlite.org/lang_altertable.html
SQLite supports a limited subset of ALTER TABLE. The ALTER TABLE command in SQLite allows the user to rename a table or to add a new column to an existing table. It is not possible to rename a colum, remove a column, or add or remove constraints from a table.
Alternatively, you can create a new table and fill the new table with data of old table.
One last and the most important point I want to say about is that if you want to change the schema of .db file exist in assets folder, then you should do that with sqlite command line. When I try to change schema by using sqlite db browser, it does not work like you said. There is a problem in sqlite db browser for changing schema.
So , if you want to change schema, do it in sqlite command line.
Increase .pragma version in both code and .db(You can increase .pragma using db browser for sqlite).
For adding removing adding data without changing schema purposes, you can safely continue to work with sqlite db browser.

Room Migration with prepopulated DB - Is fallbackToDestructiveMigration() my only option?

I have several tables in my DB of which some contain prepopulated content that the user cannot change and others that are only filled by the user. Now I want to update the prepopulated, static content but keep the user generated content.
This Android developer guide says the following concerning my question:
Because there is an implemented migration path from version 2 to version 3, Room runs the defined migrate() method to update the database instance on the device to version 3, preserving the data that is already in the database. Room does not use the prepackaged database file, because Room uses prepackaged database files only in the case of a fallback migration.
So this means I have no possibility to, for example: from 3 columns X, Y & Z - drop columns X and Z and recreate them with new content from the updated DB file, while keeping column Y as it was?
Below is an illustration of the issue.
Is it correct that I only have 2 options now:
Drop tables completely and make user lose their generated content, so they can have the updated prepopulated content
Write migrations for the new structure, but end up with empty columns X.2 and Z.2 because Room will ignore my prepopulated DB 2.0
Take a look at this library, maybe it helps you solve this
https://github.com/ueen/RoomAssetHelper
You can name tables and columns that should be preserved and then rename the existing db, copy the new one and transfer the specified columns.
Example from the GitHub page:
val db = RoomAssetHelper.databaseBuilder(applicationContext,
AppDatabase::class.java,
"chinook.db",
1,
preserve = arrayOf(TablePreserve(table = "yourTable",
preserveColumns = arrayOf("yourColumn"),
matchByColumns = arrayOf("id"))))
.build()

Android Room persistance library. Drop Table

I need to know how to "DROP FROM Table" using Room Persistence Library.
I already know that we can delete all the rows using the method:
#Query("DELETE FROM table")
void deleteAll();
However, what I need is deleting the whole table. This is because of my primary_key is an autoincrement column, so using the previous code, it is not rested.
I already read answers about this topic in the next links:
Reset auto-increment in Android's Room library
Android Room - reset auto generated key on each app run
However, I can't believe that this library doesn't provide an easier way to do this, regardless of the reason or use.
Could use Migrations that Room provides for updating the database via our own queries. As we want to make changes to the database which Room cannot resolve (yet) from the code. We could delete the table, recreate it or update it. Depending on what is needed.
Option 1: Migrate with keeping other data
First increase the version of the database: update the version parameter in the #Database annotation.
Create a migration like this:
static final Migration MIGRATION_1_2 = new Migration(1, 2) { // From version 1 to version 2
#Override
public void migrate(SupportSQLiteDatabase database) {
// Remove the table
database.execSQL("DROP TABLE my_table"); // Use the right table name
// OR: We could update it, by using an ALTER query
// OR: If needed, we can create the table again with the required settings
// database.execSQL("CREATE TABLE IF NOT EXISTS my_table (id INTEGER, PRIMARY KEY(id), ...)")
}
};
Add the migration when building the database:
Room.databaseBuilder(context, MyDatabase.class, "mydatabase")
.addMigration(MIGRATION_1_2) // Add the migration
.build();
Run the app again. If the queries were correct, the migration is done
Option 2: Migrate with losing data
There is also a fast option, but all data in the database will be cleared!
This is because the database gets recreated when using the method below.
Like option one, increment the version of the database
Use .fallbackToDestructiveMigration() when creating the database, like so:
Room.databaseBuilder(context, MyDatabase.class, "mydatabase")
.fallbackToDestructiveMigration()
.build();
Run the app. It will remove the old database and recreate it. (All earlier data is wiped)
If you want to do this with auto migration then need to use spec as autoMigrations value for #Database annotation. Look like
this
autoMigrations = {#AutoMigration(from = 1, to = 2, spec = AppDatabase.MyAutoMigration.class)}
Example like you want to delete a table(YourTableName) from database version 1 and then migrate to version 2 then the full code looks like this
#Database(
version = 2,
entities = {Entity1.class, Entity2.class},
autoMigrations = {#AutoMigration(from = 1, to = 2, spec = AppDatabase.MyAutoMigration.class)},
exportSchema = true)
#TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
#DeleteTable.Entries(value = #DeleteTable(tableName = "YourTableName"))
public static class MyAutoMigration implements AutoMigrationSpec {
}
// Your DAO 1
// Your DAO 2
}

What is the best way to generate Android Room Migrations?

Note that I come from a .NET background where I'm familiar with Entity Framework.
So in EF migrations, the actual migrations are generated for you. Basically, you specify in your POCO objects the changes you want. When you add a new migration, it does a diff between the database and the POCO objects and generates a migration script. You barely have to even look at it.
So using Android Room, I'm finding I have to handcraft these migrations myself. And there are rules that mean the system crashes unless the migration you handcraft puts the schema in exactly the state Room thinks it should be based on the POJO objects.
For me, this seems like a tedious and risky task that could be automated (like EF migrations already does). So my question is, is there a tool or something that'll do these migrations automatically? If not, what are some guidelines on how to do these migrations safely or efficently?
I'm not sure if a tool like this exists specifically for Room as of today, but I hardly doubt it, since Room's stable version was released just a few months ago.
One small thing you can get generated is SQL statements for tables creation.
Just add:
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
to android/defaultConfig in your's build.gradle and a JSON with complete schemas will be generated.
To do these migrations safely you can fully test them - this is well described here
Regarding efficiency I would recommend creating direct migrations like Migration(1, 4)
The android room migrations are problematic, because even a difference of column order will show a failed migration. But due to sqlite's limitations column type alterations and so forth cannot be done. Do as in Michal's answer and generate the schemas.
Then use a JSON diff tool to see how it generates your particular table.
If the difference is easy, do the necessary alter command in your migration script. I've pretty much given up on doing this and instead just create a new table the way my new version expects it to be, copy the data from the old table, drop it, and rename the correctly created table. Here's some code:
static final Migration MIGRATION_3_4 = new Migration(3, 4) {
#Override
/**
* Instead of trying to read the owl droppings of the error message, simply go to the schemas directory
* and do a table copy of the before and after. Look for the table creation in the updated schema number
* then do an insert by doing a select * from the old
* then remove the old and rename the new
*/
public void migrate(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS crew2 (`crewId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `shortName` TEXT, `isCaptain` TEXT)");
_db.execSQL("INSERT INTO crew2(crewid,name,shortname) SELECT * FROM crew_table");
_db.execSQL("DROP TABLE crew_table");
_db.execSQL("ALTER TABLE crew2 RENAME TO crew_table");
_db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_crew_table_shortName` ON `crew_table` (`shortName`)");
}
};

How to migrate existing SQLite application to Room Persistance Library?

It might be a bit early to ask, but is it possible and how to migrate/upgrade an existing SQLite database application to a new Android Room Persistance Library?
Assuming your room entities match your current table schemas, you can keep using the same database/tables.
Room manages a master table which is initialized on creation or upgrade of the database, so you need to increment your database version and provide a dummy migration:
#Database(entities = SomeEntity.class, version = EXISTING_VERSION + 1)
public class MyDatabase extends RoomDatabase {
// ...
}
MyDatabase db = Room.databaseBuilder(context, MyDatabase.class, "db_name")
.addMigrations(new Migration(EXISTING_VERSION, EXISTING_VERSION + 1) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// NOOP
}
}).build();
For those who are wondering if there is any way to migrate from SQLite to Room even if your schema does not match, the answer is YES, you can migrate from SQLite to room even if the schema does not match.
It is possible, but a requires quite careful conversions. As the process requires so many steps to cover, I will just leave references you can follow.
Incrementally migrate from SQLite to Room
Hope it will be helpful for a few.

Categories

Resources