Room Dao inheritance - android

Is is it possible to have a Dao with a non annotated method? And override it in the derived classes?
interface DaoBase<TEntity, TId> where TEntity : Entity<TId> {
#Insert
fun add(entity: TEntity)
fun get(id: TId): TEntity?
#Update
fun update(entity: TEntity)
}
fun <TEntity, TId> DaoBase<TEntity, TId>.addOrUpdate(entity: TEntity) where TEntity : Entity<TId> {
val entityQ = get(entity.id)
if(entityQ == null) {
add(entity)
} else {
update(entity)
}
}
I get this error for the DaoBase object:
DaoBase.java:13: error: An abstract DAO method must be annotated with one and only one of the following annotations: Insert,Delete,Query,Update,RawQuery
public abstract TEntity get(TId id);
And the same error on any Dao that inherits from it. I think the code that generates the code may have a bug?

add #Query above get
#Query("SELECT * FROM tableName WHERE id=:id")
fun get(id: TId): TEntity?
Also you can forgo your addOrUpdate function, and your update function
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(entity: TEntity)
which is the same as updating

Related

Room database generic DAO class error: name clash: insert(List<Entity>) and insert(List<? extends Entity>) have the same erasure

I'm trying to implement UPSERT (update or insert) method myself because #UPSERT annotation is only available in version 2.5.0-beta01 and I want to wait for a stable release. Here is what I did:
I write a BaseDao<T> and other DAO classes which extend the BaseDao, so then I don't have to manually write basic functions for every DAO class
#Dao
abstract class BaseDao<T> {
#Insert(onConflict = IGNORE)
abstract fun insert(obj: T): Long
#Insert(onConflict = IGNORE)
abstract fun insert(obj: List<T>): List<Long>
#Update(onConflict = REPLACE)
abstract fun update(obj: T)
#Update(onConflict = REPLACE)
abstract fun update(obj: List<T>)
#Transaction
open fun insertOrUpdate(obj: T) {
val id = insert(obj)
if (id == -1L) update(obj)
}
#Transaction
open fun insertOrUpdate(obj: List<T>) {
val insertResult = insert(obj)
val rowsToUpdate = insertResult.mapIndexedNotNull { index, rowId ->
if (rowId == -1L) null else obj[index]
}
if (rowsToUpdate.isNotEmpty()) update(rowsToUpdate)
}
}
And child DAO class:
#Dao
abstract class MyDao : BaseDao<MyEntity> {
#Insert(entity = MyEntity::class)
abstract override fun insert(obj: MyEntity): Long
#Insert(entity = MyEntity::class)
abstract override fun insert(obj: List<MyEntity>): List<Long>
#Update(entity = MyEntity::class)
abstract override fun update(obj: MyEntity)
#Update(entity = MyEntity::class)
abstract override fun update(obj: List<MyEntity>)
}
But when building the app it returns errors:
MyDao_Impl.java:398: error: name clash: insert(List<? extends MyEntity>) in MyEntity_Impl and insert(List<MyEntity>) in MyDao have the same erasure, yet neither overrides the other
MyDao_Impl.java:461: error: name clash: update(List<? extends MyEntity>) in MyEntity_Impl and update(List<MyEntity>) in MyDao have the same erasure, yet neither overrides the other
In my understanding Room doesn't understand that MyDao class is overriding the methods of BaseDao and it tries to implement the abstract functions of both MyDao and BaseDao. I just don't understand why this issue happens to insert(obj: List<MyEntity>) but insert(obj: MyEntity)
If I remove all annotations in BaseDao and make it just a normal abstract class then I have to override insertOrUpdate() function every time. This goes against the goal of BaseDao.
Do you have any ideas or guides to help me achieve that approach?

accessing kotlin Suspend function from Java code

I have Room database interface which is a Kotlin file and as I dont want the calls to run on mainthread I am using kotlin Suspend. how can I use suspend function from java
I have two method where I want to insert a User and another to retrieve the users
these are the errors I get in my java file
Code
Room Dao interface
UserDao
#Dao
interface UserDao {
fun getAllUsersAsync(): CompletableFuture<List<User>> =
GlobalScope.future { getAllUsers() }
#Query("SELECT * FROM user")
suspend fun getAllUsers(): List<User>
#Insert
suspend fun insertUser(user: User): Long
}
Java code
private Long addUser(com.i6systems.in2plane.AppDatabase.User user) {
return userDao.insertUser(user);
}
private List<com.i6systems.in2plane.AppDatabase.User> getUsers () {
return userDao.getAllUsersAsync();
}
your help is much appreciated
Thanks
R

How return completable on room transaction Android

I need your help please.
I have dao interface that save some configurations:
#Dao interface ConfigDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(config: Config)
#Update(onConflict = OnConflictStrategy.REPLACE)
fun update(config: Config)
#Query("select * from T_CONFIG where isSelected = :isSelected")
fun getConfig(isSelected: Boolean): Single<Config>
#Query("select * from t_config")
fun getConfigAll(): LiveData<MutableList<Config>>
#Query("update T_CONFIG set isSelected = :isSelected where idEnvironment = :id")
fun updateConfigById(id: String, isSelected: Boolean):Completable
#Transaction
fun updateConfigTransaction(configSelected: Config){
if (configSelected.idEnvironment == Environtment.Type.PRD.toString()){
updateConfigById(Environtment.Type.PRD.toString(), false)
updateConfigById(Environtment.Type.DEV.toString(), true)
}else{
updateConfigById(Environtment.Type.PRD.toString(), true)
updateConfigById(Environtment.Type.DEV.toString(), false)
}
}
}
I need to know when the transaction is complete with success or error.
I tried to implement Completable from io.reactivex but it's not possible.
Since Room 2.1.0
Additional Async Support: DAO methods annotated with #Insert, #Delete or #Update, along with #Query containing INSERT, DELETE or UPDATE statements, now support Rx return types Completable, Single, Maybe, and Guava's return type ListenableFuture, and they can also be suspend functions.
Source: https://developer.android.com/jetpack/androidx/releases/room#2.1.0
Older versions
Change the interface to an abstract class. You'll have to prefix all methods without implementation with abstract. Then:
abstract class ConfigDao(private val db: MyDatabase) {
private val scheduler = Schedulers.from(db.queryExecutor)
// Make sure the method is open so Room can generate the transaction handling code.
#Transaction
open fun updateConfigTransaction(configSelected: Config){
// ...
}
fun updateConfigTransactionAsync(configSelected: config): Completable {
return Completable
.fromAction { updateConfigTransaction(config) }
.subscribeOn(scheduler)
}
}
subscribeOn(db.queryExecutor) makes sure the query runs on the same thread as all other DAO methods returning RxJava types. Replace MyDatabase constructor parameter with whatever your database class is.

Room DAO with inherited interfaces

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();
}

Reusable generic base class DAOs with Android Room

Is there any way to create reusable generic base class DAOs with Android Room?
public interface BaseDao<T> {
#Insert
void insert(T object);
#Update
void update(T object);
#Query("SELECT * FROM #{T} WHERE id = :id")
void findAll(int id);
#Delete
void delete(T object);
}
public interface FooDao extends BaseDao<FooObject> { ... }
public interface BarDao extends BaseDao<BarEntity> { ... }
I haven't been able to figure out any way of achieving this without having to declare the same interface members and write the query for each sub class. When dealing with a large number of similar DAOs this becomes very tedious...
Today, August 08, 2017, with version 1.0.0-alpha8 the Dao below works. I can have other Dao heroing the GenericDao.
#Dao
public interface GenericDao<T> {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(T... entity);
#Update
void update(T entity);
#Delete
void delete(T entity);
}
However, GenericDao can not be included in my Database class
Generic findAll function:
base repository and dao:
abstract class BaseRepository<T>(private val entityClass: Class<T>) {
abstract val dao: BaseDao<T>
fun findAll(): List<T> {
return dao.findAll(SimpleSQLiteQuery("SELECT * FROM ${DatabaseService.getTableName(entityClass)}"))
}
}
interface BaseDao<T> {
#RawQuery
fun findAll(query: SupportSQLiteQuery): List<T>
}
database service:
object DatabaseService {
fun getEntityClass(tableName: String): Class<*>? {
return when (tableName) {
"SomeTableThatDoesntMatchClassName" -> MyClass::class.java
else -> Class.forName(tableName)
}
}
fun getTableName(entityClass: Class<*>): String? {
return when (entityClass) {
MyClass::class.java -> "SomeTableThatDoesntMatchClassName"
else -> entityClass.simpleName
}
}
}
example repo and dao:
class UserRepository : BaseRepository<User>(User::class.java) {
override val dao: UserDao
get() = database.userDao
}
#Dao
interface UserDao : BaseDao<User>
I have a solution for findAll.
Codes in that BaseDao:
...
public List<T> findAll() {
SimpleSQLiteQuery query = new SimpleSQLiteQuery(
"select * from " + getTableName()
);
return doFindAll(query);
}
...
public String getTableName() {
// Below is based on your inheritance chain
Class clazz = (Class)
((ParameterizedType) getClass().getSuperclass().getGenericSuperclass())
.getActualTypeArguments()[0];
// tableName = StringUtil.toSnakeCase(clazz.getSimpleName());
String tableName = clazz.getSimpleName();
return tableName;
}
...
#RawQuery
protected abstract List<T> doFindAll(SupportSQLiteQuery query);
and other Dao looks like :
#Dao
public abstract class UserDao extends AppDao<User> {
}
That's all
The idea is
Get the table name of subclass's generic type on runtime
Pass that table name to a RawQuery
If you prefer interface to abstract class, you can try optional method of java 8.
It's not beautiful but worked, as you can see.
I created a gist at here
AFAIK, you can do it only for insert(), update(), and delete(), as it doesn't require specific SQL statement that needs to be verified at compile time.
example:
BaseDao.java
public interface BaseDao<T> {
#Insert
void insert(T obj);
#Insert
void insert(T... obj);
#Update
void update(T obj);
#Delete
void delete(T obj);
}
UserDao.java
#Dao
abstract class UserDao implements BaseDao<User> {
#Query("SELECT * FROM User")
abstract List<User> getUser();
}
source
It should be implemented as following:
interface BaseDao<T : BaseEntity> {
#Insert
fun insert(entity: T)
#Insert
fun insert(vararg entities: T)
#Update
fun update(entity: T)
#Delete
fun delete(entity: T)
fun getAll(): LiveData<List<T>>
}
#Dao
abstract class MotorDao : BaseDao<Motor> {
#Query("select * from Motor")
abstract override fun getAll(): LiveData<List<Motor>>
}
Although I agree with your thinking, the answer is no. For several reasons.
When the fooDao_Impl.java is generated from your #Dao fooDao extends BaseDao<Foo> class, you will be met with a lot of "cannot find class Symbol T" errors. This is due to the method Room uses to generate dao implementations. This is a method that will not support your desired outcome, and is unlikely to change soon (in my opinion, due to type erasure).
Even if this is resolved, Room does not support dynamic #Dao queries, in an effort to prevent SQL injection. This means you can only dynamically insert values into queries, not column names, table names, or query commands. In the example you have you could not use #{T} as it would breach this principle. In theory if the problem detailed in point 1 is resolved you could user insert, delete and update though.

Categories

Resources