I have a database setup in android right now. the database and tables work fine. However, when I want to add a new table and use onUpgrade - the database is created, but doesn't properly work. As in the table looks created but I can't add data to it. when I try the app crashes. All the version 1 tables still look fine.
here is what my onUpgrade looks like:
private val newDatabase = listOf<String>(
"Database1"
)
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
newDatabase.forEach{
val table2 = "CREATE TABLE $it ($ID INTEGER PRIMARY KEY, $AVERAGETIME TEXT DEFAULT \"0\",$DATE TEXT DEFAULT \"0\")"
db?.execSQL(table2)
}
}
my oncreate looks exactly the same, just uses a different array of strings:
override fun onCreate(db: SQLiteDatabase?) {
originalDatabase.forEach {
val table1 =
"CREATE TABLE $it ($ID INTEGER PRIMARY KEY, $AVERAGETIME TEXT DEFAULT \"0\",$DATE TEXT DEFAULT \"0\")"
db?.execSQL(table1)
}
}
Is this the best way to add a new table? once the table is made, it seems to be blank and not work right. even though I have default values set to it.
I tried looking through other posts but can't seem to find one concrete answer
Here is a working example Delivery Droid Database Class
Related
I added a column to a table, then added the following migration (version 56 to 57):
private val MIGRATION_56_57 = object : Migration(56, 57) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `app_stage` ADD COLUMN hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 NOT NULL")
}
}
After building and releasing the app to our existing users, they get a migration error and the app crashes. To correct the error, I just need to change:hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 NOT NULL
to:hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 .
Should I just add another migration from version 57 to 58 as:
private val MIGRATION_57_58 = object : Migration(57, 58) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `app_stage` ADD COLUMN hasSeenBusinessOwnerQuestion INTEGER DEFAULT 0 ")
}
}
?
Will existing users upgrading from version 56 all the way to 58 get that same migration error? How do I add another migration to version 58 to avoid migration errors?
? Will existing users upgrading from version 56 all the way to 58 get that same migration error?
No Room will invoke all the migrations in sequence (56-57 and then 57-58) and then after all the migrations have been performed continue with the database build.
However you cannot use the ALTER command to ADD an already existing column, which would be the case going from 57-58. So the migration would fail.
Although later versions of SQLite (3.35.0) support ALTER TABLE .... DROP COLUMN ...., this version is not available at present on Android devices and that there is no ALTER COLUMN. You will have to use an alternative means to alter the column.
The DROP COLUMN is also quite restrictive
You could do the following (where ? represents the table in question):-
DROP TABLE IF EXISTS ?_old
this is just in-case it exists (it should not)
Use the ALTER TABLE ? RENAME TO ?_old (_old just a suggested name for what is to be a temporary version of the table)
Use CREATE TABLE IF NOT EXISTS ....
Room demands that the create table SQL creates the table according to how it interprets the class annotated with #Entity.
It is suggested that you retrieve the create table SQL from the generated java that is available after compiling the project. The SQL will be in the createAllTables method of the class that is the same as the class that is annotated with #Database but suffixed with _Impl
Use INSERT INTO ? SELECT * FROM ?_old to copy existing data into the newly created version of the table
Use DROP TABLE IF EXISTS ?
So apart from the CREATE TABLE .... (which would have to be altered, see points above) the following would cater for all scenarios (new users, users on 57 and users on 56) :-
private val MIGRATION_57_58 = object : Migration(57, 58) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS `app_stage_old`")
database.execSQL("ALTER TABLE `app_stage` RENAME TO `app_stage_old`")
/* NOTE the CREATE TABLE IF NOT EXISTS .... SHOULD BE ALTERED ACCORDINGLY */
database.execSQL("CREATE TABLE IF NOT EXISTS `app_stage` (`id` INTEGER, `name` TEXT NOT NULL,`hasSeenBusinessOwnerQuestion` INTEGER DEFAULT 0, PRIMARY KEY(`id`))")
database.execSQL("INSERT INTO `app_stage` SELECT * FROM `app_stage_old`")
database.execSQL("DROP TABLE IF EXISTS `app_stage_old`")
}
}
In my Android application I use Room library for persistency.
Assuming, I have an Entity defined like this:
#Entity(tableName = "my_entity")
public class MyEntity {
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
private int id;
//...
}
can I rely on the fact, that id will be increased monotonically, i.e. that for newly inserted row id will always be higher, than for all previously created rows?
I think, that it is unlikely, but I can imagine, that Room (or SQLite - I am not sure, who is responsible in this case) could e.g. try to reuse the IDs of the previously deleted rows...
As far as I can see, the official documentation does not tell anything about it PrimaryKey.AutoGenerate().
This answer is the expanded comment from JensV.
As suggested by JensV, the generated schema json file contains (among others):
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ... <other fields>)"
So looking at the SQLite docs of AUTOINCREMENT we get, that it is guaranteed to be monotonic.
In fact, this flag serves exactly for this purpose: to ensure, that the generated value is monotonic (without this flag, the value still will be generated to be unique, but will not be necessarily monotonic). Taking into account, that Room uses the flag, it is strange, that they don't mention it in the documentation.
While searching for this, I only came across people asking how to Avoid inserting duplicate rows using room db. But my app has a feature where the user may tap a copy button and the list item will get inserted again in the db. I could have simply achieved this if my table didn't have a primary key set on one of its fields. While I found this solution for SQLite, I don't know how I can achieve this in Room Db. Because while writing an insert query with custom queries in room would defeat the purpose of using room in the first place.
Let's say you have some entity
#Entity(tableName = "foo_table")
data class Foo (
#PrimaryKey(autoGenerate = true) var id: Int,
// or without autogeneration
// #PrimaryKey var id: Int = 0,
var bar:String
)
and you have some Dao with insert:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(foo: Foo)
Then to copy your existing value (copiedValue: Foo) you need in some way to manage your primary key:
Scenario 1. Your Primary Key is autogenerated, you have to set it to default value to get new autogenerated one:
copiedValue.id = 0
yourDao.insert(copiedValue)
Scenario 2. Your Primary Key is not autogenerated, you have to set new primary key manually:
copiedValue.id = ... // some code to set new unique id
yourDao.insert(copiedValue)
Issue
My app is crashing because I am not handling migration properly. I'm looking for a solution to migrate the name of 1 column in my table.
In my project I a have a room table named 'content' with a Double attribute 'archivedCount'. In the latest version of the app the attribute archivedCount attribute is re-named to dismissCount, still as type Double.
Original Content model
#Entity(tableName = "content")
data class Content(#PrimaryKey var id: String, var archiveCount: Double) : Parcelable {...}
New Content model
#Entity(tableName = "content")
data class Content(#PrimaryKey var id: String, var dismissCount: Double) : Parcelable {...}
Attempted Solution
After reading a Google Developer Advocate's explanation Understanding migrations with Room, I attempted her solution outlined in the post's section Migrations with complex schema changes which entails making a copy of the original table, deleting the old table, then renaming the newly created table.
With the following approach below there is a runtime error on this line: database.execSQL("INSERT INTO content_new (id, dismissCount) SELECT id, archiveCount FROM users"); because I already cleared my app's cache so the old table no longer exists.
Can I update a single column without re-creating the entire table?
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// Create the new table
database.execSQL(
"CREATE TABLE content_new (id TEXT, dismissCount REAL, PRIMARY KEY(id))");
// Copy the data
database.execSQL("INSERT INTO content_new (id, dismissCount) SELECT id, archiveCount FROM users");
// Remove the old table
database.execSQL("DROP TABLE content");
// Change the table name to the correct one
database.execSQL("ALTER TABLE content_new RENAME TO content");
}
};
Solution
Thanks to the guidance from #TimBiegeleisen we discovered that the Android implementation of SQLite 3.19 for API 27 and 28 has not yet upgraded to the version 3.25 SQLite which allows this feature outlined in this StackOverflow post.
Once Android upgrades a command such as this to alter a table column will be possible: database.execSQL("ALTER TABLE content RENAME COLUMN archiveCount TO dismissCount")
There is a solution without migration - use ColumnInfo:
data class Content(#PrimaryKey var id: String, #ColumnInfo(name = "archiveCount") var dismissCount: Double) : Parcelable{...}
Database column will be still archiveCount, but in Kotlin property will be renamed.
I'm trying to set my primary key to one after deleting all items in table:
#Query("DELETE FROM myTable")
fun deleteTable()
#Query("DELETE FROM sqlite_sequence WHERE name = 'myTable'")
fun clearPrimaryKey()
but it doesn't work and after adding items again, their number don't start from 1. Any idea how to clear primary key in Room library?
Let us say your table name is myTable. Then just do this:
#Query("ALTER TABLE myTable AUTO_INCREMENT = 1")
fun clearPrimaryKey()
It resets the number of auto-generated primary key.
UPDATE:
Another way to do both deleting and resetting is this:
#Query("TRUNCATE TABLE myTable;")
fun deleteAndResetPrimaryKey()