How to get autogenerated primary key of newly inserted entity? - android

I have an entity defined like this:
#Entity
data class Data(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
var name: String,
)
And I insert an instance into the database with this method:
#Insert
fun insert(data: Data)
The instance on insertion might look like this:
MainActivity.db.dao().insert(Data(0, "Blub"))
Now I would like to know the autogenerated primary key that was generated upon insertion. How can I obtain it?

With:
#Insert
fun insert(data: Data): Long
This will return the rowId. rowId is not necessarily your integer, but it will be a primary key I would think it's always the same as the auto-generated integer, but I can't find any true documentation on that.

Related

How to ignore primary key when I insert room database entity

How do I ignore the primary key when I insert some entity?
Room Entity has to have more than 1 primary key.
For example, there is an entity following under.
#Entity(primaryKeys = ["id"], tableName = "someEntity")
data class SomeEntity(
val id: Int = 0,
val someClass: SomeClass<*>? = null
)
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(obj: SomeClass): Completable
Parameter "obj" will have two column(fields).
When I have insert logic like that,
do I have to care about id (with autoGenerate annotation) column?
When I insert SomeEntity with dao,
I can only get SomeClass<*>? type, without id: Int.
Does #AutoGenerate annotation on column id can solve my problem?
Room understands val id: Int = 0 if it is marked with #PrimaryKey as a value to be ignored when inserting.
#Entity
data class SomeEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
val someClass: SomeClass<*>? = null
)
and when creating a new instance SomeEntity(someClassInstance) is completely fine.
Note: if SomeClass isn't a basic class that SQL is able to save, you need to have some way to serialize it.

How do I pass my Data object to DAO and access all data object properties in Query?

I have simple data object that I want to insert into room database but as I am using auto increment on my primary key I am doing it as below
#Dao
interface T1Dao {
#Query("INSERT INTO tbl_t1(data1, data2) VALUES ( :T1.data1, :T1.data2) ")
fun insert(note: T1): Long
}
I have many properties in T1 so I don't want pass them separately if possible.
In above example I am just showing two properties.
But you can just use #Insert and not to set your primary key field, can't you?
#Insert
fun insert(note: T1): Long
Let's say you have T1 class:
#Entity
data class T1(
#PrimaryKey(autoGenerate = true)
val id: Int = 0, // This lets you not to set id before inserting
val data1: String,
val data2: String
)
Then you can insert:
dao.insert(T1(data1 = "data1", data2 = "data2")) // just don't set id

Primary keys in Room should be Int or Long?

I'm designing a Database that would be implemented in Android using Room, after reading the docs I found that there is no recomendations about using Int or Long as primary keys.
In some places they define entities with int primary keys:
#Entity
data class User(
#PrimaryKey var id: Int,
var firstName: String?,
var lastName: String?
)
But in other place it says that if you want to get the ID of the last row inserted "insert" method return a long.
#Dao
interface MyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
#Insert
fun insertBothUsers(user1: User, user2: User)
#Insert
fun insertUsersAndFriends(user: User, friends: List<User>)
}
If the #Insert method receives only 1 parameter, it can return a long, which is the new rowId for the inserted item. If the parameter is an array or a collection, it should return long[] or List instead.
So, The primary keys in room should be Int or Long?; Are there best practices about choosing one type over the other?
Both of these types will map to an INTEGER in the underlying SQLite database.
For example, with a class like this:
#Entity
data class Test(#PrimaryKey val i: Int, val l: Long)
You'd get a SQLite table defined with this query:
CREATE TABLE IF NOT EXISTS `Test` (`i` INTEGER NOT NULL, `l` INTEGER NOT NULL, PRIMARY KEY(`i`))
So you can use whichever one you'll need the magnitude of in your code. If you do decide to use an Int for some reason and you run out of values, you can even change it to a Long without having to migrate your database later on.
As for this INTEGER type, as per the SQLite documentation:
The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
Both is fine. On mobile (and most of the time in general) Int should be sufficient (it will also save you 4 bytes over Long).
Why? Using an Int you could store over 2 billion records (2_000_000_000). So you could store a record of around 1/4 of all the humans living on earth. Just for comparison: Using a Long would enable you to store over 900 quadrillion records (900_000_000_000_000_000).
I would recommend to use Long since the insert function will return a Long, or long[].

Room automatically sorts on the basis of primary key in Android

I have a data class like this
#Entity
data class Question(
#field:SerializedName("question")
var question: String? = null,
#field:SerializedName("answers")
var answers: ArrayList<String?>? = null,
#field:SerializedName("id")
#PrimaryKey
var id: Int? = null
)
Then in DAO I have saving and getting methods like this
#Dao
interface QnADao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveQuestion(questions:Question)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveAllQuestions(questions: List<Question?>?)
#Query("SELECT * from Question")
fun getAllQnA():List<Question>
}
I am saving a list of Questions and then later on retrieving them. So whenever I retrieve them I get the list sorted according to the id which is the primary key.
So if I am saving questions with id:254, id:23, id:45 and id:92 then I am getting it like this id:23, id:45, id:92 and id:254
But I don't need a sorted list like that, I need to get the data as it was saved in the database. Any help would be appreciated. Thanks.
Try to use autoGenerate = true for primary key so it will create PK number in sequence
See below lines
#PrimaryKey(autoGenerate = true)
So that now your insert and retrive order will be same
You can add a Date field to your Question entity
#field:SerializedName("date")
var date: Date? = null,
and order your entities by date
#Query("SELECT * FROM Question ORDER BY date DESC")

Why can't Anko ignore the passed value of _id when _id is INTEGER + PRIMARY_KEY+ AUTOINCREMENT?

I have designed the _id field is INTEGER + PRIMARY_KEY+ AUTOINCREMENT, I use the code SettingManage().addSetting(MSetting(10L,"My Settings",2000L,"This is description!")) to insert a record to the table.
I think Anko will ignore the passed value 10 of _id and pass a new value to _id automatically, but in fact the value 10 of _id is inserted into the table.
How can make Anko ignore the passed value of _id when _id is INTEGER + PRIMARY_KEY+ AUTOINCREMENT ?
Insert Data
SettingManage().addSetting(MSetting(10L,"My Settings",2000L,"This is description!"))
Design Table
class DBSettingHelper(mContext: Context = UIApp.instance) : ManagedSQLiteOpenHelper(
mContext,
DB_NAME,
null,
DB_VERSION) {
companion object {
val DB_NAME = "setting.db"
val DB_VERSION = 5
val instance by lazy { DBSettingHelper() }
}
override fun onCreate(db: SQLiteDatabase) {
db.createTable( DBSettingTable.TableNAME , true,
DBSettingTable._ID to INTEGER + PRIMARY_KEY+ AUTOINCREMENT ,
DBSettingTable.Name to TEXT,
DBSettingTable.CreatedDate to INTEGER,
DBSettingTable.Description to TEXT
)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.dropTable(DBSettingTable.TableNAME, true)
onCreate(db)
}
}
class DBSetting(val mMutableMap: MutableMap<String, Any?>) {
var _id: Long by mMutableMap
var name: String by mMutableMap
var createdDate: Long by mMutableMap
var description: String by mMutableMap
constructor(_id: Long, name: String, createdDate: Long, description: String)
: this(HashMap()) {
this._id = _id
this.name = name
this.createdDate = createdDate
this.description=description
}
}
object DBSettingTable {
val TableNAME = "SettingTable"
val _ID = "_id"
val Name = "name"
val CreatedDate = "createdDate"
val Description="description"
}
data class MSetting (
val _id: Long,
val name: String,
val createdDate: Long,
val description: String
)
Business Logic
class SettingManage {
fun addSetting(mMSetting:MSetting){
DBSettingManage().addDBSetting(DbDataMapper().convertMSetting_To_DBSetting(mMSetting))
}
}
class DBSettingManage(private val mDBSettingHelper: DBSettingHelper =DBSettingHelper.instance) {
fun addDBSetting(mDBSetting: DBSetting)=mDBSettingHelper.use{
insert(DBSettingTable.TableNAME,*mDBSetting.mMutableMap.toVarargArray())
}
}
class DbDataMapper {
fun convertMSetting_To_DBSetting(mMSetting: MSetting) =with(mMSetting){
DBSetting(_id,name,createdDate,description)
}
fun convertDBSetting_To_MSetting(mDBSetting: DBSetting)=with(mDBSetting){
MSetting(_id,name,createdDate,description )
}
}
fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T): List<T> =
parseList(object : MapRowParser<T> {
override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
})
Anko, in your usage, is a wrapper for SQLite. SQL itself overrides auto increment when a custom value is passed. If no value is passed -> Automatic value. Otherwise -> manual. It assumes it's unique because of PRIMARY_KEY, but that's a different problem.
As far as I know, there is no integrated feature into Anko that allows for overriding this manually. Instead, the only thing you can do is not pass a value. Any SQL query that's wrong won't be caught by Anko itself, but by the raw SQL. It's SQL that throws any exceptions meaning Anko won't check for missing data.
Simply, don't pass an ID. You can write a function that still takes in the ID but discards it if the row is set to PRIMARY_KEY and AUTO_INCREMENT.
I have done more digging in the source code and there's no support for ignoring passed values if it's set to auto increment.
This means the only way you can get it to actually automatically increment is by not passing any values. If you are using manual queries of any kind, you simply don't pass any value. So, don't give the database the ID. Because it's auto increment it'll automatically add it and that's on the base SQL level.
Ank doesn't ignore it by default because it's essentially a wrapper for SQLite. Meaning it has to be able to be passed if the ID should be overridden (see the link in the second sentence of this answer). As such, Anko ignoring it would cause more problems than it'd do good. You have to make sure it isn't passed instead

Categories

Resources