如何编写Room数据库适配器包装查询结果类型?实现类RxJava2的包装机制
如何为Room数据库查询结果包装特定类型(以协程为例)
哈哈,这个问题我刚好研究过!Room确实提供了灵活的方式来包装查询结果,不管是用官方支持的协程类型,还是自定义自己的包装类都可以,下面给你详细说:
一、优先用Room原生支持的协程返回类型
其实Room早就官方支持协程了,完全不需要自己去造类似android.arch.persistence.room:rxjava2的工件,直接就能把查询结果包装成协程友好的类型:
1. 单次查询:用suspend函数
如果是只需要查询一次的场景,直接给Dao方法加上suspend修饰符,返回实体或者列表就行,Room会自动处理异步调用:
@Dao interface UserDao { // 查询单个用户,返回可空的User对象 @Query("SELECT * FROM users WHERE id = :id") suspend fun findUserById(id: String): User? // 查询所有用户,返回列表 @Query("SELECT * FROM users") suspend fun getAllUsers(): List<User> }
调用的时候只需要在协程作用域里调用这些方法就行,比如在ViewModel里:
viewModelScope.launch { val user = userDao.findUserById("123") // 处理用户数据 }
2. 可观察查询:用Flow
如果需要监听数据库变化(数据更新时自动收到新结果),可以让Dao方法返回Flow类型,Room会自动帮你监听数据库,有变化就发射新数据:
@Dao interface UserDao { // 返回Flow,当该用户数据变化时自动发射新值 @Query("SELECT * FROM users WHERE id = :id") fun findUserByIdFlow(id: String): Flow<User?> // 返回Flow,当用户表有任何变化时发射整个列表 @Query("SELECT * FROM users") fun getAllUsersFlow(): Flow<List<User>> }
收集Flow的时候同样在协程里操作:
viewModelScope.launch { userDao.findUserByIdFlow("123").collect { user -> // 每次用户数据变化都会走到这里 updateUserUI(user) } }
二、自定义包装类型:通过CallAdapter.Factory扩展
如果你的需求是包装成Room不支持的自定义类型(比如你自己实现的Observable类),那可以参考rxjava2库的实现思路,通过自定义CallAdapter.Factory来扩展Room的能力:
步骤1:定义你的自定义包装类型
比如我们自己写一个MyObservable类,用来包装查询结果:
class MyObservable<T>(private val source: Flow<T>) { // 简单实现一个订阅方法,内部用协程收集Flow fun subscribe(onResult: (T) -> Unit) { CoroutineScope(Dispatchers.IO).launch { source.collect(onResult) } } }
步骤2:实现CallAdapter和CallAdapter.Factory
这个工厂类的作用是告诉Room:当Dao方法返回MyObservable类型时,用我们的适配器来处理查询结果:
// 实现CallAdapter,负责把Room的查询结果转换成MyObservable class MyObservableCallAdapter<T>(private val responseType: Type) : CallAdapter<T, MyObservable<T>> { override fun responseType(): Type = responseType override fun adapt(call: Call<T>): MyObservable<T> { // 把Room的Call转换成Flow,用Room提供的asFlow扩展方法 val resultFlow = call.asFlow() return MyObservable(resultFlow) } } // 实现Factory,让Room识别MyObservable类型 class MyObservableCallAdapterFactory : CallAdapter.Factory() { override fun get( returnType: Type, annotations: Array<Annotation>, room: RoomDatabase ): CallAdapter<*, *>? { // 检查返回类型是不是MyObservable if (getRawType(returnType) != MyObservable::class.java) { return null } // 获取MyObservable的泛型参数类型(比如MyObservable<User>里的User) val observableType = getParameterUpperBound(0, returnType as ParameterizedType) return MyObservableCallAdapter<Any>(observableType) } }
步骤3:给RoomDatabase添加自定义工厂
在创建RoomDatabase实例的时候,把我们的工厂加进去:
val appDatabase = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "my_app_db" ) .addCallAdapterFactory(MyObservableCallAdapterFactory()) .build()
步骤4:在Dao里使用自定义类型
现在就可以在Dao里直接返回MyObservable了:
@Dao interface UserDao { @Query("SELECT * FROM users WHERE id = :id") fun findUserById(id: String): MyObservable<User?> }
总结
- 90%的场景下,用Room原生支持的
suspend函数和Flow就完全够用了,官方已经做了最优化的异步处理和数据库监听,没必要自己造轮子。 - 如果确实需要自定义包装类型,通过
CallAdapter.Factory扩展Room是完全可行的,思路和rxjava2库的实现一致。
内容的提问来源于stack exchange,提问作者Oussaki
相关产品推荐
相关产品推荐

