英文:
Update Local database without affecting the old database while uploading app to playstore
问题
在本地数据库中有2个字段 (例如:名称,密码)。现在我将应用程序上传到Play商店。之后,我在数据库中添加了一个字段,即手机号码。因此,现在数据库有3个字段 (即名称,密码,手机号码)。那么,如果我将这个应用程序上传到Play商店,会发生什么?它会影响旧用户的数据库吗?我如何更新该数据库,而不影响用户的旧本地数据库?我正在使用Room数据库。
英文:
I have 2 fields in the local database (For eg. Name, Password). Now I uploaded the app to the Play Store. After that, I added one field in the database which is mobile number. So now the database has 3 fields (i.e Name, Password, Mobile Number). Now, what happens if I upload this app to the Play Store? Will it affect the database of the old users? How can I update that database without affecting the old local database of the users? I'm using Room Database.
答案1
得分: 1
以下是您要翻译的内容:
"The update will be rolled out, via PlayStore, to old users unless it is a different App."
"You MUST update the old users otherwise the App will crash. However, you can retain their data but you must cater for the new column."
"As the schema has changed (a new column) and if there isn't a migration old users will experience a crash as Room checks to see if the schema, as per the @Entity
annotated class (what is expected) against the database (what is found)."
"The crash would be along the lines of: 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. Expected identity hash: e843da3b4913dbc0880c558d759fe82, found: d5c32de20cfd495f9eae5463c1ec7433
"hashes will differ (expected(1st) is as per the @Entity
the found is as per the schema in the existing database)"
"What you need to do is
set the default value to a suitable value that indicates that no mobile number has been provided, and
add a migration that introduces the new column, and
increase the version number (which will invoke the migration, perform the migration and then processing continues to the check/open of the database).
if there is no Migration then a crash will ensue e.g. java.lang.IllegalStateException: A migration from 1 to 2 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.
"
"Demo"
"The following is a demo that will first create the App with the database at V1 without the Mobile field/column and then will migrate the existing database when the database is upgraded to V2. The existing users will have a value that indicates no mobile."
"First the Database code for both versions with the V2 code commented out (The Migration doesn't need to be commented out but would obviously not be present for V1 (just saves having to repeat code)):-"
"The Database version is increased:"
"2 new const vals are added:"
"The User class becomes:"
"The @Database annotated class TheDatabase has the migration added:"
"The commented out activity code is un-commented for V2:"
"When the App is run then App Inspection now shows:"
"As can be seen:-"
"Fred and Mary have the recognizable indicator that the mobile wasn't provided, i.e., it is xxxxxxxxxx"
"Alice has been added as part of the Migration (not that this would normally be included, it is just to show that the migration was performed)"
"Jane and John have been added with their provided mobile numbers"
"Pat has been added with the default value, as per the field default value (the database default value cannot be applied as mobile is not nullable)"
"Final Test"
"The remaining proof of concept is when a new user installs the App, i.e., a fresh/new install. In this scenario, for the demo, just the three V2 users will be inserted (Jane, John, and Pat):"
"Obviously the inserts are reflecting what the App user may do"
英文:
The update will be rolled out, via PlayStore, to old users unless it is a different App.
You MUST update the old users otherwise the App will crash. However, you can retain their data but you must cater for the new column.
As the schema has changed (a new column) and if there isn't a migration old users will experience a crash as Room checks to see if the schema, as per the @Entity
annotated class (what is expected) against the database (what is found).
- The crash would be along the lines of:
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. Expected identity hash: e843da3b4913dbc08880c558d759fe82, found: d5c32de20cfd495f9eae5463c1ec7433
- hashes will differ (expected(1st) is as per the
@Entity
the found is as per the schema in the existing database)
What you need to do is
- set the default value to a suitable value that indicates that no mobile number has been provided, and
- add a migration that introduces the new column, and
- increase the version number (which will invoke the migration, perform the migration and then processing continues to the check/open of the database).
- if there is no Migration then a crash will ensue e.g.
java.lang.IllegalStateException: A migration from 1 to 2 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.
- if there is no Migration then a crash will ensue e.g.
Demo
The following is a demo that will first create the App with the database at V1 without the Mobile field/column and then will migrate the existing database when the database is upgraded to V2. The existing users will have a value that indicates no mobile.
First the Database code for both versions with the V2 code commented out (The Migration doesn't need to be commented out but would obviously not be present for V1 (just saves having to repeat code)):-
const val DATABASE_VERSION = 1 /*<<<<<<<<<< WILL CHANGE to 2 FOR V2 */
const val USER_TABLE_NAME = "user"
const val USER_NAME_COLUMN = "name"
const val USER_PASSWORD_COLUMN = "password"
@Entity(tableName = USER_TABLE_NAME)
data class User(
@PrimaryKey
@ColumnInfo(name = USER_NAME_COLUMN)
val name: String, /* Original */
@ColumnInfo(name = USER_PASSWORD_COLUMN)
val password: String /* Original */
@Dao
interface UserDAOs {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(user: User): Long
@Query("SELECT * FROM user")
fun getAllUsers(): List<User>
}
@Database(entities = [User::class], exportSchema = false, version = DATABASE_VERSION)
abstract class TheDatabase: RoomDatabase() {
abstract fun getUserDAOs(): UserDAOs
companion object {
private var instance: TheDatabase?=null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries() /* for brevity of the demo */
.build()
}
return instance as TheDatabase
}
}
}
Now some activity code to load some V1 data:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: UserDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getUserDAOs()
dao.getAllUsers() /*<<<< force open the database in case no code runs (this when the version and schema checking and migration for V2 will take place ) */
if (DATABASE_VERSION == 1) {
dao.insert(User("Fred", "passwordFred")) /* Original */
dao.insert(User("Mary", "passwordMary")) /* Original */
}
/* commented out for V1 as mobile not a field in the User */
/*
if (DATABASE_VERSION == 2) {
dao.insert(User("Jane","passwordJane","1234567890"))
dao.insert(User("John","passwordJohn","0987654321"))
dao.insert(User("Pat","passwordPat"))
}
*/
}
}
When run for a fresh install (aka old user) then the database, via App Inspection:-
- room_master_table is where the schema hash is stored and will be the found
- as expected the two rows exist and have expected values.
Next the code is changed.
The database code becomes:-
The Database version is increased:-
const val DATABASE_VERSION = 2 /*<<<<<<<<<< WILL CHANGE to 2 FOR V2 */
2 new const vals are added:-
const val USER_MOBILE_COLUMN = "mobile" /*<<<<<<<<<< ADDED for V2 */
const val USER_MOBILE_DEFAULT_VALUE = "xxxxxxxxxx" /*<<<<<<<<<< ADDED for V2 */
The User class becomes:-
@Entity(tableName = USER_TABLE_NAME)
data class User(
@PrimaryKey
@ColumnInfo(name = USER_NAME_COLUMN)
val name: String, /* Original */
@ColumnInfo(name = USER_PASSWORD_COLUMN)
val password: String /* Original */ ,/*<<<<<<<<< ADDED comma FOR V2 */
/*<<<<<<<<<< SCHEMA CHANGES FOR V2 (see comma above) >>>>>>>>>>*/
@ColumnInfo(name = USER_MOBILE_COLUMN, defaultValue = USER_MOBILE_DEFAULT_VALUE) /*<<<<<<<<<< ADDED FOR V2 */
val mobile: String = "not provided" /*<<<<<<<<<< ADDED for V2 (default value allows mobile to not be given for V1 code in Main Activity)*/
)
The @Database annotated class TheDatabase has the migration added:-
@Database(entities = [User::class], exportSchema = false, version = DATABASE_VERSION)
abstract class TheDatabase: RoomDatabase() {
abstract fun getUserDAOs(): UserDAOs
companion object {
private var instance: TheDatabase?=null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries() /* for brevity of the demo */
.addMigrations(MIGRATE_1_to_2)
.build()
}
return instance as TheDatabase
}
val MIGRATE_1_to_2: Migration = object: Migration(1,2){
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE $USER_TABLE_NAME ADD COLUMN $USER_MOBILE_COLUMN TEXT NOT NULL DEFAULT '$USER_MOBILE_DEFAULT_VALUE'")
/* So as to show Migration add a row when migrating (would not be done normally) */
val cv = ContentValues()
cv.put(USER_NAME_COLUMN,"Alice")
cv.put(USER_PASSWORD_COLUMN,"passwordAlice")
cv.put(USER_MOBILE_COLUMN,"1111111111")
db.insert(USER_TABLE_NAME,OnConflictStrategy.IGNORE,cv)
}
}
}
}
The commented out activity code is un-commented for V2:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: UserDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getUserDAOs()
dao.getAllUsers() /*<<<< force open the database in case no code runs */
if (DATABASE_VERSION == 1) {
dao.insert(User("Fred", "passwordFred")) /* Original */
dao.insert(User("Mary", "passwordMary")) /* Original */
}
/* commented out for V1 as mobile not a field in the User */
if (DATABASE_VERSION == 2) {
dao.insert(User("Jane","passwordJane","1234567890"))
dao.insert(User("John","passwordJohn","0987654321"))
dao.insert(User("Pat","passwordPat"))
}
}
}
When the App is run then App Inspection now shows:-
As can be seen:-
- Fred and Mary have have the recognisable indicator that the mobile wasn't provided i.e. it is xxxxxxxxxx
- Alice has been added as part of the Migration (not that this would normally be included, it is just to show that the migration was performed)
- Jane and John have been added with their provided mobile numbers
- Pat has been added with the default value, as per the field default value (the database default value cannot be applied as mobile is not nullable)
Final Test
The remaining proof of concept, is when a new user installs the App i.e. a fresh/new install. In this scenario , for the demo, just the three V2 users will be inserted (Jane, John and Pat):-
Obviously the inserts are reflecting what the App user may do
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论