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?
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?
I'm studying Android and I'm new to DB. I am trying to use the Room library to use the DB.
I'm looking at the sample code, but there's something I don't understand.
It is to call the abstract method of the AppDatabase abstract class without implementing it.
At least as far as I know, abstract classes cannot be instantiated.
But I'm curious how it can be called and how to use the returned value.
(Same for Kotlin.)
Am I wrong about JAVA or Kotlin?
ToDoDao.interface
#Dao // Data Access Object
interface ToDoDao {
#Query("SELECT * FROM ToDo")
abstract fun getAll(): List<ToDo>
#Insert
void insert(ToDo todo)
#Update
void update(ToDo todo)
#Delete
void delete(ToDo todo)
}
AppDatabase.class
#Database(entities = [Todo.class], version = 1)
abstract class AppDatabase extends RoomDatabase {
public abstract void TodoDao todotDao();
}
Main.class
AppDatabase db = Room.databaseBuilder(this, AppDatabase.class, "todo-db").build();
mResultTextView.setText(db.todoDao().getAll().toString); // THIS
We don't build database like this for simplicity , this and that are an example you can refer for building database and for accessing it use
val DB = AppDatabase.getInstance(context).ToDoDao ()
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
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.