In room, it seems to be impossible to use annotation based setups in a generic class with variable based data or with provided classes - the result is that there is no workaround to define queries with Flow inside an abstract generic base class.
Is that really try?
Examples 1 - CAN BE SOLVED
Define a query which contains a table name that is defined by a class variable
#Dao
abstract class BaseDao<Item : IItem, ItemWithRef> {
abstract val tableName: String
// DOES NOT WORK - because table name is not compile-time constant
#Transaction
#Query("select * from ${tableName}")
abstract suspend fun loadAll(): List<ItemWithRef>
// SOLUTION
private val rawQueryLoadAll
get() = "SELECT * FROM $tableName"
#Transaction
#RawQuery
protected abstract suspend fun loadAll(query: SimpleSQLiteQuery): List<ItemWithRef>
suspend fun loadAll(): List<ItemWithRef> = loadAll(queryLoadAll)
}
Examples 2 - CAN NOT BE SOLVED?
Define flow queries which contains a table name that is defined by a class variable
Here the problem is, that #RawQuery needs to know the queries classes - can this somehow be solved as well?
#Dao
abstract class BaseDao<Item : IItem, ItemWithRef> {
abstract val tableName: String
// all 3 possibilities DO NOT WORK
// - because `#RawQuery` needs to know that it handles `ItemWithRef::class`
// - because the table name is not constant
// DOES NOT WORK
#Transaction
#Query("select * from ${tableName}")
abstract suspend fun flowAll(): Flow<List<ItemWithRef>>
// DOES NOT WORK
#Transaction
#RawQuery
protected abstract fun flowAll(query: SimpleSQLiteQuery): Flow<List<ItemWithRef>>
fun flowAll(): Flow<List<ItemWithRef>> = flowAll(queryLoadAll)
// DOES NOT WORK
#Transaction
#RawQuery(observedEntities = arrayOf(ItemWithRef::class))
protected abstract fun flowAll(query: SimpleSQLiteQuery): Flow<List<ItemWithRef>>
fun flowAll(): Flow<List<ItemWithRef>> = flowAll(queryLoadAll)
}
Question
I'm fine with the workaround for example 1 but is there any workaround to also define a Flow raw query in a base class somehow?
Related
I am trying to update partial fields in my Android Room database.
I am using #Update with a partial class:
#Update(entity = BehaviorDataSent::class)
fun update(obj: BehaviorDataSentUpdate)
#Entity(tableName = "BehaviorDataSent")
data class BehaviorDataSent(#PrimaryKey val actionTime: Long, val netName: String? = null, val savedTime: Long = 0, val sentTime: Long? = null)
data class BehaviorDataSentUpdate(val actionTime: Long, val sentTime: Long )
However, I get an error saying that the update method must have a body.
What have I missed?
It would appear that the #Update is in an abstract class rather than an interface. As such it needs to be an abstract function (or have a body).
so :-
#Update(entity = BehaviorDataSent::class)
abstract fun update(obj: BehaviorDataSentUpdate)
or change abstract class .... to interface
and/or you could use :-
#Query("UPDATE behaviordatasent SET sentTime=:sentTime WHERE actionTime=:actionTime")
abstract fun update(sentTime: Long, actionTime: Long)
in which case the BehaviourDataSentUpdate class is not required.
if in an interface then abstract should not be coded.
i.e. in an interface abstract is implied and thus does not need to be coded.However, in an abstract class functions without bodies need to be abstract and thus you can also have non-abstract functions that then need a body.
What is the best way to use coroutines with LiveData for selecting some data from database using Room.
This is My Dao class with suspended selection
#Dao
interface UserDao {
#Query("SELECT * from user_table WHERE id =:id")
suspend fun getUser(id: Long): User
}
Inside of View Model class I load user with viewModelScope.
Does it correct way to obtain user entity ?
fun load(userId: Long, block: (User?) -> Unit) = viewModelScope.launch {
block(dao.getUser(userId))
}
According developer android mentioned
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
This chunk of code does not work
Your Room must return LiveData.
Use instead:
#Dao
interface UserDao {
#Query("SELECT * from user_table WHERE id =:id")
fun getUser(id: Long): LiveData<User>
}
I would like to add value, date and details to the current pb. I am receiving an error 'conflicting declaration' in the database for pbInfo. How should I fix this error?
#Entity(tableName = "pb_table")
data class Pb(#PrimaryKey
val pb: String)
#Entity
data class PbInfo(#PrimaryKey
var value: Double,
var date: Int,
var details: String)
#Dao
interface PbInfoDao {
#Insert
fun update(vararg pbInfo: PbInfo): LongArray
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.pbDao(), database.pbInfo())
}
}
}
suspend fun populateDatabase(pbDao: PbDao, pbInfoDao: PbInfoDao) {
pbDao.deleteAll()
var pb = Pb("Squat")
pbDao.insert(pb)
var pbInfo = PbInfo(122.5, 28, "I was feeling on top form today!")
First of all, you have two Entities in a single class (possibly the conflict)
So, add separate class for separate Entity.
Then, in your RoomDatabase abstract class, add two Entity Classes like this (and also create separate Dao interface classes):
#Database(entities = [(Pb::class), (Pbinfo::class)] ,version = 2)
abstract class YourRoomDatabaseClass: RoomDatabase(){
...
abstract fun pbDao() : PbDao
abstract fun pbinfoDao(): PbinfoDao
...
}
This should solve the conflicting of Entity classes. I have a single database with two Entities just like this and running without any problems. (Please mind me because I don't know Kotlin Syntax)
Use this
#Insert(onConflict = OnConflictStrategy.REPLACE)
instead of
#Insert
I want to create a DAO Object with a custom function like this
#Dao
interface DataAccessObjDao{
#Insert
fun insert(someEntity: SomeEntity)
#Ignore
fun sampleFun(){
insert(SumEntity())
}
}
but compiler complains about sample fun
Class 'DataAccessObjDao_Impl' must either be declared abstract or implement abstract method 'sampleFun()' in 'DataAccessObjDao
#Ignore is for property or entity, can not be used on a method.
You can do this by extending your interface :
fun DataAccessObjDao.sampleFun(){
// irrelevant code
}
or by adding #Transaction
#Transaction
fun sampleFun(){
firstDelete()
thenInsert()
}
As you wrote, Dao can also be abstract class. You can define a method with body inside abstract class. However Dao is for querying tables and have different Dao objects for different tables. It would be better if they had only insert, delete, update and select queries. I also BaseDao to minimize Dao code.
You should call dao.insert(SumEntity()) inside a LocalDataSource class which you use Repository like in this guide.
I have a DAO interface, of which I have multiple implementations and I want one of them to be a Room implementation (Kotlin):
interface BaseDao {
fun getAll(): Single<List<SomeData>>
fun insert(data: List<SomeData>)
}
and my Room (RxRoom) interface:
#Dao
interface RxRoomBaseDao: BaseDao {
#Query("SELECT * FROM some_data")
override fun getAll(): Single<List<SomeData>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
override fun insert(data: List<SomeData>)
}
It looks like the Room compiler tries to compile the BaseDao instead of the RxRoomBaseDao and complains error: Dao class must be annotated with #Dao and for both methods error: A DAO method can be annotated with only one of the following:Insert,Delete,Query,Update.
I have also tried an abstract class for the RxRoomBaseDao with the same result.
Is this a known bug or limitation of the Room compiler? How can I achieve what I want without polluting my BaseDao with Room's annotations?
It seems OP has since moved on from Room, but fwiw, it's possible to use generics to solve this problem:
interface BaseDao<T> {
#Insert
fun insert(vararg obj: T)
}
#Dao
abstract class DataDao : BaseDao<Data>() {
#Query("SELECT * FROM Data")
abstract fun getData(): List<Data>
}
Check out the "Use DAO's inheritance capability" section in this Official Room blog post
Create your dao as an abstract class:
#Dao
public abstract class EntityDao implements Base {
#Override
#Query("SELECT * FROM " + Entity.TABLE_NAME)
public abstract Flowable<List<Entity>> getEntities();
}
interface Base {
Flowable<List<Entity>> getEntities();
}