How to autogenerate a Room database id without providing an id - android

I am trying to create a room database and I want each item inserted into it to have its own unique id without me having to provide it, The problem is when I try to insert new items into the database I get an error asking me to provide an id.
Here is my entity:
#Entity(tableName = "notes_table")
data class Note(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
#ColumnInfo(name = "description")
val description: String,
#ColumnInfo(name = "priority")
var priority: Int)
Is there a way to have the database create its own auto-generated auto-increasing id column without having me having to add it like this:
val item = Note(id, item, priority)
insert(item)
And instead do this:
val item = Note(item, priority)
insert(item)

Create a constructor that takes item and priority as arguments
#Entity(tableName = "notes_table")
data class Note (var item: String,
#ColumnInfo(name = "priority")
var priority: String) {
#PrimaryKey(autoGenerate = true)
var id: Long = 0,
//.....
}

You can just simply give the id a default value and put that at the end:
#Entity(tableName = "notes_table")
data class Note(
#ColumnInfo(name = "description")
val description: String,
#ColumnInfo(name = "priority")
var priority: Int)
#PrimaryKey(autoGenerate = true) //must be at the end
val id: Int = 0 //Long type recommend
)
Then you can:
val item = Note(item, priority)
insert(item)

Because your data class Note has three parameter.
So you you have to create Note by passing three parameter.
It is nothing to do with autogenerate or room.

Related

Null Data Returned with Nested Relation in Room Database Android

Given that I have 3 entities, Order contains list of LineItem, each LineItem will associates with one Product by productId.
The problem that when I get data from OrderDao, it returns null for the product field, but in the lineItem field, it has data. While I can data with ProductWithLineItem.
Already tried a lot of work arounds but it does not work.
Here is my code for entities and dao
Entities
#Entity(tableName = DataConstant.ORDER_TABLE)
data class Order(
#PrimaryKey
#ColumnInfo(name = "orderId")
val id: String,
#ColumnInfo(name = "status")
var status: String
)
#Entity(tableName = DataConstant.LINE_ITEM_TABLE)
data class LineItem(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "lineItemId")
val id: Long,
#ColumnInfo(name = "productId")
val productId: String,
#ColumnInfo(name = "orderId")
val orderId: String,
#ColumnInfo(name = "quantity")
var quantity: Int,
#ColumnInfo(name = "subtotal")
var subtotal: Double
)
#Entity(tableName = DataConstant.PRODUCT_TABLE)
data class Product(
#PrimaryKey
#NonNull
#ColumnInfo(name = "productId")
val id: String,
#ColumnInfo(name = "name")
var name: String?,
#ColumnInfo(name = "description")
var description: String?,
#ColumnInfo(name = "price")
var price: Double?,
#ColumnInfo(name = "image")
var image: String?,
)
Relations POJOs
data class ProductAndLineItem(
#Embedded val lineItem: LineItem?,
#Relation(
parentColumn = "productId",
entityColumn = "productId"
)
val product: Product?
)
data class OrderWithLineItems(
#Embedded var order: Order,
#Relation(
parentColumn = "orderId",
entityColumn = "orderId",
entity = LineItem::class
)
val lineItemList: List<ProductAndLineItem>
)
Dao
#Dao
interface OrderDao {
#Transaction
#Query("SELECT * FROM `${DataConstant.ORDER_TABLE}` WHERE orderId = :id")
fun getById(id: String): Flow<OrderWithLineItems>
}
Result after running with Dao
Result after running query
Here is my code for entities and dao
You code appears to be fine, with the exception of returning a Flow, testing, using your code, but on the main thread using List (and no WHERE clause) i.e the Dao being :-
#Query("SELECT * FROM ${DataConstant.ORDER_TABLE}")
#Transaction
abstract fun getOrderWithLineItemsAndWithProduct(): List<OrderWithLineItems>
Results in :-
The data being loaded/tested using :-
db = TheDatabase.getInstance(this)
orderDao = db.getOrderDao()
orderDao.clearAll()
orderDao.insert(Product("product1","P1","desc1",10.01,"image1"))
orderDao.insert(Product("product2","P2","desc2",10.02,"image2"))
orderDao.insert(Product("product3","P3","desc3",10.03,"image3"))
orderDao.insert(Product("product4","P4","desc4",10.04,"image4"))
orderDao.insert(Product("","","",0.0,""))
val o1 = orderDao.insert(Order("Order1","initiaited"))
val o2 = orderDao.insert(Order("Order2","finalised")) // Empty aka no List Items
val o1l1 = orderDao.insert(LineItem(10,"product3","Order1",1,10.01))
val o1l2 = orderDao.insert(LineItem(20,"product4","Order1",2,20.08))
val o1l3 = orderDao.insert(LineItem(30,"","Order1",3,30.09))
val o1l4 = orderDao.insert(LineItem(40,"","x",1,10.01))
//val o1l3 = orderDao.insert(LineItem(30,"no such product id","Order1",10,0.0))
// exception whilst trying to extract if not commented out at test = ....
val TAG = "ORDERINFO"
val test = orderDao.getOrderWithLineItemsAndWithProduct()
for(owl: OrderWithLineItems in orderDao.getOrderWithLineItemsAndWithProduct()) {
Log.d(TAG,"Order is ${owl.order.id} status is ${owl.order.status}")
for(pal: ProductAndLineItem in owl.lineItemList) {
Log.d(TAG,"\tLine Item is ${pal.lineItem.id} " +
"for Order ${pal.lineItem.orderId} " +
"for ProductID ${pal.lineItem.productId} " +
"Quantity=${pal.lineItem.quantity} " +
"Product description is ${pal.product.description} Product Image is ${pal.product.image} Price is ${pal.product.price}")
}
}
As such I believe the issue might be that for some reason the Flow is detecting when the first query has completed but prior to the underlying queries.
That is when using #Relation the core objects (Order's) are extracted via the query and the core objects created then the related objects are extracted by a another query and used to build ALL the related objects as a List (unless just the one when it doesn't have to be a list). So prior to this underlying query the core object will have a null or an empty list for the underlying objects. Of course with a hierarchy of #Relations then this is replicated along/down the hierarchy.
I would suggest temporarily adding .allowMainThreadQueires to the databaseBuilder and using a List<OrderWithLineItems> or just a sole OrderWithLineItems. If using this then you get the Product(s) then the issue is with the Flow (which is what I suspect).

Loop within data class declaration in Kotlin?

Apologies, I am fairly new to Kotlin!
What I have:
#entity(tableName = "main_table")
data class Table(
#PrimaryKey(autoGenerate = true) var tableId: Int,
#ColumnInfo(name = "column_name") val columnName: String?,
...
)
What I would like:
val columnList = listOf(...list of strings...)
#entity(tableName = "main_table")
data class Table(
#PrimaryKey(autoGenerate = true) var tableId: Int,
for (column in columnList)
#ColumnInfo(name = column+"_name") val column+Name: String?,
...
)
Motivation: that list will be re-used in other parts of the code, as such, it would be great if it only existed within the code once.
Two unknowns here for me, can that loop be done somehow? And can concatenation be done during the variable declaration?
Thank you for reading!

Android Room Database Create Entity Object without Id

My entity class:
#Entity(tableName = "student")
data class Student(
#PrimaryKey(autoGenerate = true)
val id: Long,
val name: String,
val age: Int,
val gpa: Double,
val isSingle: Boolean
)
The problem is, since the id is auto-generated by the Room database - means no matther what I put for the id into the constructor it will be overridden anyway, and because it is one of the parameter in the constructor, I have to give the id every time like this:
val student = Student(0L, "Sam", 27, 3.5, true)
How can I avoid making up the id so I can just put in the neccessary data like this:
val student = Student("Sam", 27, 3.5, true)
Don't place the id in the constructor:
#Entity(tableName = "student")
data class Student(
val name: String,
val age: Int,
val gpa: Double,
val isSingle: Boolean
) {
#PrimaryKey(autoGenerate = true)
var id: Long? = null
}
How can I avoid making up the id
Just set default value to 0 (or null)
#Entity(tableName = "student")
data class Student(
#PrimaryKey(autoGenerate = true)
val id: Long = 0, <-- default value (or use null)
id is auto-generated by the Room database - means no matther what I put for the id into the constructor it will be overridden anyway
Not really like that. If you set id explicitly then this id will be used on insert.
If you want the auto increment id for your case just in your entity set the type of id to Int and for the id value parameter in your constructor use the null value and this handle the work for you .

Why do I have to make a setter for an auto-incremented column in a database?

In my android app I have created a database using the room persistance library. It contains of a table called stack with the columns stack_id, col_1, col_2 and stack_name. Here's the code for the entity class:
#Entity
class Stack(
#ColumnInfo(name = "stack_name") val stackName: String,
#ColumnInfo(name = "col_1") val column1: String,
#ColumnInfo(name = "col_2") val column2: String
) {
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val
stackId: Int = 0
}
Question 1: Is the stack_id correctly implemented? I found this solution to set en auto-incremented column to 0 and it will automaticly auto-increment the value, but it doesn't make sense to me. So, is this correct?
Question 2: Whe I want to build the app, it throws the error:
Cannot find setter for field. private final int stackId = 0;
But it would be nonsense to make a setter for an auto-incremented value. So, should I make a setter or is there another solution?
You are using concrete class so you need to provide get() and set() for properties.
Use data class which are build for this purpose only .
#Entity
data class Stack(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val id: Int? = null,
#ColumnInfo(name = "stack_name") val stackName: String,
#ColumnInfo(name = "col_1") val column1: String,
#ColumnInfo(name = "col_2") val column2: String)
You need to move
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val
stackId: Int = 0
to the constructor.
#Entity
class Stack(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "stack_id") val
stackId: Int = 0,
#ColumnInfo(name = "stack_name") val stackName: String,
#ColumnInfo(name = "col_1") val column1: String,
#ColumnInfo(name = "col_2") val column2: String
)
Why this works? The property is val, so is final/immutable, but the assignment in the constructor is a default value which can be overridden by passing in a value.
However in your example the property is in the class body and is assigned the value 0 on init, and cannot be changed as property is immutable.
The second option, as pointed out, is making the property mutable by using var instead of val.

Check if an entry is already in a livedata list wihout looping through the list

I am trying to build an app to help me track some of the tasks we have to do in the game.
I have a Firebase Firestore database that store all the tasks and I download at the application launch the data and add only the one I don't have.
Here is my entry model:
#Entity(tableName = "entry_table")
data class Entry(
#PrimaryKey(autoGenerate = true) var uid: Long?,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String,
#ColumnInfo(name = "target") val target: Int = 0,
#ColumnInfo(name = "position") val position: Int = 0,
#ColumnInfo(name = "starred") val starred: Boolean = false
) {
constructor(): this(null, "", "", 0, 0, starred = false)
}
Since I download the document from the firestore database I cannot set an ID before inserting the entries in my SQLite database.
This means that I cannot use the "contains" method on my livedata list (since the entries I recieve has a "null" id and the one from the database has an id). I need to loop though all the data, here is the code:
#WorkerThread
suspend fun insertEntry(entry: Entry) {
for (doc in entriesList.value!!){
if (doc.description == entry.description && doc.title == entry.title) {
Log.d("MAIN_AC", "Entry already saved $entry")
return
}
}
entryDAO.insertEntry(entry)
}
My code works but I am not satisfied with it, is there a better way to make this happen? I was hoping that the contains method could ignore some arguments (in my case the autogenerated ID)
One way you can go about, assuming you are using Room, it is to annotate your insert function (in the relevant DAO) with OnConflictStrategy.IGNORE.
e.g.
#Dao
interface EntryDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(list: List<Entry>)
// or (if you want the inserted IDs)
// fun insert(list: List<Entry>) : LongArray
}
Be sure to also annotate your entity with the relevant unique index.
e.g.
#Entity(tableName = "entry_table",
indices = [Index(value = ["title", "description"], unique = true)]
)
data class Entry(
#PrimaryKey(autoGenerate = true) var uid: Long,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String
//...
)
Primary keys should not be null-able, you can .map to Entry wit uid = 0. If you are using the same entity model both locally and remotely that is probably not the best idea.

Categories

Resources