Reusable generic base class DAOs with Android Room

前端 未结 6 2252
夕颜
夕颜 2020-12-23 18:30

Is there any way to create reusable generic base class DAOs with Android Room?

public interface BaseDao {

  @Insert
  void insert(T object);

  @Up         


        
相关标签:
6条回答
  • 2020-12-23 18:57

    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

    0 讨论(0)
  • 2020-12-23 19:00

    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

    0 讨论(0)
  • 2020-12-23 19:05

    Although I agree with your thinking, the answer is no. For several reasons.

    1. 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).

    2. 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.

    0 讨论(0)
  • 2020-12-23 19:13

    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

    1. Get the table name of subclass's generic type on runtime
    2. 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

    0 讨论(0)
  • 2020-12-23 19:17

    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>
    
    0 讨论(0)
  • 2020-12-23 19:19

    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>>
    
    }
    
    0 讨论(0)
提交回复
热议问题