I'm using room database, and I want to query table column as kotlin Tuple. Can anyone help me in same?
So I have a table named User which has multiple fields and Now I want to query 2 specific fields first_name and last_name and Kotlin Tuples(Pair)
#Entity(tableName = "user")
data class User(
var id: String,
var first_name: String,
var last_name: String
// some other fields
)
#Dao
interface UserDao {
#Query("SELECT first_name as first, last_name as second FROM user WHERE id = :id")
fun getUserName(id: String): Pair<String, String>
}
I'm getting a compiler error like below:
Not sure how to convert a Cursor to this method's return type (kotlin.Pair<java.lang.String,java.lang.String>)
This is how you can achieve it.
Create the class for Tuples. Include all the field which you want to get. In your case, the first and last names are the fields. This is concept is called Returning subsets of columns.
UserNameTuple
data class UserNameTuple(
#ColumnInfo(name = "first_name") val firstName: String?,
#ColumnInfo(name = "last_name") val lastName: String?
)
Design the query like this:
#Query("SELECT first_name, last_name FROM user WHERE id = :id")
fun getUserName(id: String): List<UserNameTuple>
You will get the data that satisfied the query condition with the tuple data.
This is how you can achieve it in your application.
You can also use secondary constructor in you User Class like below:
constructor(first_name: String, last_name: String) : this("", first_name, last_name)
Or since you have more field then you can use default value to avoid secondary constructor like below [I put "", you can change it according your requirements]:
data class User(
var id: String = "",
var first_name: String = "",
var last_name: String = ""
// some other fields
)
And modify getUserName like this
#Query("SELECT first_name, last_name FROM user WHERE id = :id")
fun getUserName(id: String): User
Related
I have a query like this in my room Dao
#Query("SELECT * FROM my_data ORDER BY id ASC LIMIT 1")
suspend fun getFirstItem(): MyEntity?
and MyEntity is just a data class with an auto generated id.
#Entity(tableName = "my_data")
data class MyEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
#ColumnInfo(name = "date_created")
var dateCreated: String? = null,
#ColumnInfo(name = "description")
var description: String? = null
)
When I run the query the entity object that is returned has always id=0.
How can I get the actual id of the row using a query in room?
EDIT: This is the function I'm calling the query
suspend fun getFirstItem(context: Context): MyEntity? {
val db = MyDatabase.getDb(context)
return db.MyDao().getFirstItem()
}
The returned MyEntity object is something like this.
MyEntity(id=0, dateCreated="2022-12-07 09:38:37", description="some description")
Where the id is always returned as 0, although it isn't actually 0
In My System, Your Query works Properly.
All Available Entries
Query Result
Do invalid catch/Restart and Try again
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).
I have an Room Entity called City:
#Entity(tableName = "cities")
class City(
#PrimaryKey
#ColumnInfo(name = "unique_city_id")
val id: Long,
#ColumnInfo(name = "city_name")
val name: String,
#ColumnInfo(name = "city_code")
val code: String,
)
And I have a list of objects of this class type:
data class CoffeeHouse(
override val id: Long,
override val latitude: Double,
override val longitude: Double,
override val city: City?,
override val address: String,
)
I need to save both CoffeeHouse and City classes. Because there are a lot of identical cities, I map a list of coffeehouses to a set of cities to get only unique ones:
val cities = coffeeHouses.map { it.city?.toPersistenceType() }.toSet()
(.toPersistenceType() just maps domain type to persistence)
And then I'm inserting coffeeHouses and cities into Room Database using these DAOs:
#Dao
abstract class CoffeeHouseDao(val cacheDatabase: CacheDatabase) {
private val cityDao = cacheDatabase.cityDao()
#Insert(onConflict = REPLACE)
abstract suspend fun insertAllCoffeeHouses(coffeeHouses: List<CoffeeHouse>)
#Transaction
open suspend fun insertAllCoffeeHousesInfo(
coffeeHouses: List<CoffeeHouse>,
cities: Set<City?>,
) {
insertAllCoffeeHouses(coffeeHouses)
cityDao.setCities(cities)
}
}
#Dao
interface CityDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setCities(cities: Set<City?>)
The problem is when I'm trying to insert Set<City?> app crashes with an exception:
Uncaught exception java.lang.NullPointerException:
Attempt to invoke virtual method 'long com.coffeeapp.android.persistence.entity.City.getId()'
on a null object reference
Stacktrace points on the line of cities insertion, so I don't understand how to make it right.
This is happening because you have set the ID field in city as the Primary Key for that table and it cannot be null.
You can try changing your annotation to
#PrimaryKey(autoGenerate = true)
Or if you do not want auto increment you have to make sure that the id is not null whenever you are inserting a City.
I think it is because of the city: City? and cities: Set<City?> in the CofeeHouse entity. Try to make them not nullable.
To allow for inserting a with null you can use :-
#Entity(tableName = "cities")
class City(
#PrimaryKey
#ColumnInfo(name = "unique_city_id")
val id: Long?, //<<<<<<<< ? ADDED
#ColumnInfo(name = "city_name")
val name: String,
#ColumnInfo(name = "city_code")
val code: String,
)
As such id will be generated when it is inserted. e.g. the following (based upon for reduced for convenience).
However, the REPLACE conflict strategy will never result in replacement as null will generate a unique id.
What I believe you want is that either city name, the city code or both (together or independently) constitutes a unique entry.
As such :-
#Entity(
tableName = "cities",
indices = [
/*
probably just one of these all three is overkill
*/
Index(value = ["city_name"],unique = true),
Index(value = ["city_code"], unique = true),
Index(value = ["city_name","city_code"],unique = true)
]
)
class City(
#PrimaryKey
#ColumnInfo(name = "unique_city_id")
val id: Long?,
#ColumnInfo(name = "city_name")
val name: String,
#ColumnInfo(name = "city_code")
val code: String,
)
As an example consider the following :-
cityDao.setCities(setOf<City>(City(null,"Sydney","SYD1"),City(null,"New York","NY1")))
cityDao.setCities(setOf<City>(City(null,"Sydney","SYD1"),City(null,"New York","NY1")))
So an attempt is made to add the same set of cities The result is:-
i.e. The first added Sydney and New York with id's 1 and 2, the second attempt replaced due to the conflict which deletes the originals so you end up with id's 3 and 4. Without the unique index(s) then the result would have been 4 rows with id's 1,2,3 and 4.
It works well to query record MVoice using fun listVoice():LiveData<List<MVoice>> with Room framework in Android Studio with Kotlin.
Now I hope to query record of part fields (Such as ID and name) of MVoice, how can I do?
interface DBVoiceDao{
#Query("SELECT * FROM voice_table ORDER BY createdDate desc")
fun listVoice():LiveData<List<MVoice>>
/* How can I do this?
#Query("SELECT id, name FROM voice_table ORDER BY createdDate desc")
fun listVoiceOfPartField():LiveData< ??? >
*/
}
#Entity(tableName = "voice_table", indices = [Index("createdDate")])
data class MVoice(
#PrimaryKey (autoGenerate = true) #ColumnInfo(name = "id") var id: Int = 0,
var name: String = "Untitled",
var path: String = "My path",
var createdDate: Calendar = Calendar.getInstance(),
var isStar: Boolean = false,
var description: String="My description"
)
As "Florina Muntenescu" suggested that Read only what you need in this article 7 Pro-tips for Room
You can achieve it by making a new Model Class:
data class VoiceMinimal(#ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "name") val name: String)
In the DAO class, we define the query and select the right columns from the voice table.
#Dao
interface DBVoiceDao {
#Query(“SELECT id, name FROM voice_table ORDER BY createdDate dESC)
fun getVoiceMinimal(): List<VoiceMinimal>
}
I'm following an Android development tutorial that uses Room to store data and retrieve it.
I have the following entity:
#Entity
data class DogBreed(
#ColumnInfo(name = "breed_id")
val breedId: String?,
#ColumnInfo(name = "dog_name")
val dogBreed: String?,
#ColumnInfo(name = "life_span")
val lifeSpan: String?,
#ColumnInfo(name = "breed_group")
val breedGroup: String?,
#ColumnInfo(name = "bred_for")
val bredFor: String?,
val temperament: String?,
#ColumnInfo(name = "dog_url")
val imageUrl: String?
) {
#PrimaryKey(autoGenerate = true)
var uuid: Int = 0
}
and the following Room DAO:
#Dao
interface DogDao {
#Insert
suspend fun insertAll(vararg dogs: DogBreed): List<Long>
#Query("SELECT * FROM dogbreed")
suspend fun getAllDogs(): List<DogBreed>
#Query("SELECT * FROM dogbreed WHERE uuid = :dogId")
suspend fun getDog(dogId: Int): DogBreed?
#Query("DELETE FROM dogbreed")
suspend fun deleteAllDogs()
}
The insertAll method works fine. I can retrieve all data using getAllDogs() and the uuid is auto-generated as intended. However, the getDog(dogId) method does not work. It always returns null.
It's a simple query and I don't see anything wrong with it. What could be the issue?
The query is used in the following code:
val dao = DogDatabase(getApplication()).dogDao()
val dog = dao.getDog(uuid)
Toast.makeText(getApplication(), "Dog from DB ${uuid} / $dog", Toast.LENGTH_SHORT).show()
The toast shows something like "Dog from DB 12 / null".
It turned out that the uuid, which was supplied as an action parameter when navigating from a different fragment, was not the correct value. It was the breedId instead of the uuid property.
I used Room a couple of time but it seems your uuId is an Int type and you are using a String type in your query. As far as I remember you have to use like for sql queries.
My guess is that you have to use both Int values or String values but not a mixed of it. And if you are using String type use Like instead of equal.
"SELECT * FROM dogbreed WHERE uuid like :dogId"