【问题标题】:Paging library with custom model class which differs from SQLite model class - Android具有不同于 SQLite 模型类的自定义模型类的分页库 - Android
【发布时间】:2019-06-24 16:24:48
【问题描述】:

我正在关注 Android Developers 的 Paging Library Overview,它使用 DataSource.Factory 从数据库中获取数据:

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM Concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

但是,就我而言,我没有表 Concert,而是表 ConcertAA,它以不同的方式存储值。

例如,我的Concert 类是:

data class Concert(val date: Long, val bands: List<Band>)

而我的ConcertAA Active Android 课程是:

@Table(name = "Concerts")
class ConcertAA(): Model(){

    @Column(name = "Bands")
    var bands: String? = null

    @Column(name = "Date", index = true)
    var date: Long? = null

}

我将乐队保存为 Json 字符串。

因此,我的问题是我如何拥有ConcertDao,在查询我的数据库时,我将每个ConcertAA 对象转换为要在列表中使用的Concert 对象?由于查询SELECT * FROM Concerts ORDER BY date DESC 将返回ConcertAA 的列表,而不是Concert 的列表。

【问题讨论】:

    标签: android dao android-paging


    【解决方案1】:

    尽管下一个会回答我自己的问题,但对于数据可能发生变异的数据库-网络逻辑,这个解决方案还不够,因为它不处理已保存在数据库中的数据的更新。

    请原谅抽象类的数量 - 我通常为扩展和可重用性工作。此外,还请原谅 Java 和 Kotlin 代码的混合。由于某种原因,某些库在我使用 Kotlin 的项目中无法识别,但在这些情况下可以随意使用 Kotlin。

    所以基本上我们需要创建一个新的DataSource

    abstract class MyDataSource<Value>: PositionalDataSource<Value>() {
    
        override fun loadInitial(params: PositionalDataSource.LoadInitialParams, callback: PositionalDataSource.LoadInitialCallback<Value>) {
            callback.onResult(getDataFromDatabase(params.requestedLoadSize, params.requestedStartPosition)?: listOf(), 0)
        }
    
        override fun loadRange(params: PositionalDataSource.LoadRangeParams, callback: PositionalDataSource.LoadRangeCallback<Value>) {
            callback.onResult(getDataFromDatabase(params.loadSize, params.startPosition)?: listOf())
        }
    
        abstract fun getDataFromDatabase(limit: Int, offset: Int): List<Value>?
    }
    

    实现的Concert数据类:

    class ConcertDataSource : MyDataSource<Concert>() {
    
        override fun getDataFromDatabase(limit: Int, offset: Int): List<Concert>? {
            return ConcertAA.getConcerts(limit, offset)
        }
    }
    

    然后我们需要一个 DataSourceFactory 来创建我们的 DataSource 实例:

    abstract class MyDataSourceFactory<Key, Value> : DataSource.Factory<Key, Value>() {
    
        val mutableLiveData: MutableLiveData<DataSource<Key, Value>>? = null
    
        override fun create(): DataSource<Key, Value> {
            val dataSource = createDataSource()
            mutableLiveData?.postValue(dataSource)
            return dataSource
        }
    
        abstract fun createDataSource(): DataSource<Key, Value>
    }
    

    以及为 ConcertDataSource 实现的 DataSourceFactory:

    class ConcertDataSourceFactory : MyDataSourceFactory<Int, Concert>() {
    
        override fun createDataSource(): DataSource<Int, Concert> {
            return ConcertDataSource()
        }
    }
    

    然后是ViewModelPagedList,它将拥有我们的数据源:

    public class ConcertViewModel extends ViewModel {
        public final LiveData<PagedList<Concert>> concertList;
        private FetchCallback callback;
        private final int pageSize = 10
    
        PagedList.BoundaryCallback<Concert> boundaryCallback = new PagedList.BoundaryCallback<Concert>(){
            boolean frontLoaded =false;
    
            public void onZeroItemsLoaded() {
    //            callback.fetch(...);
            }
    
            public void onItemAtFrontLoaded(@NonNull Concert itemAtFront) {
                if(!frontLoaded) {
    //                callback.fetch(...);
                }
                frontLoaded = true;
            }
    
            public void onItemAtEndLoaded(@NonNull Concert itemAtEnd) {
    //            callback.fetch(...);
            }
        };
    
        public ConcertViewModel(FetchCallback callback) {
            this.callback = callback;
            ConcertDataSourceFactory dataSourceFactory = new ConcertDataSourceFactory();
            PagedList.Config config = new PagedList.Config.Builder()
                    .setPageSize(pageSize)
                    .setInitialLoadSizeHint(pageSize)
                    .setEnablePlaceholders(false)
                    .build();
            concertList = new LivePagedListBuilder<>(dataSourceFactory, config).setBoundaryCallback(boundaryCallback).build();
        }
    
        public void refresh() {
            conertList.getValue().getDataSource().invalidate();
        }
    }
    

    我们使用BoundaryCallback 来侦听当 PagedList 需要加载更多时,只要它到达列表的任一侧并且我们的数据库数据没有存储任何额外数据。这将能够从服务器(或任何数据提供者)获取更多数据。使用自定义回调调用 fetch:

    interface FetchCallback{
        fun fetch(limit: Int?, concertIdOffset: String?)
    }
    

    现在,由于我们需要将回调传递给ViewModel,我们需要告诉ViewModelProviders 如何使用参数创建ViewModel 的新实例。这是使用ViewModelProvider.Factory

    abstract class MyViewModelFactory(
            private val callback: FetchCallback) : ViewModelProvider.Factory {
    
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return createViewModel(callback)
        }
    
        abstract fun <T : ViewModel> createViewModel(callback: FetchCallback): T
    }
    

    我们为 Concert 实现的ViewModelFactory

    class ConcertViewModelFactory(callback: FetchCallback) : MyViewModelFactory(callback) {
    
        override fun <T : ViewModel> createViewModel(callback: FetchCallback): T {
            return ConcertViewModel(callback) as T
        }
    }
    

    最后,我们从我们的视角初始化ViewModelActivityFragment等):

    val factory = ConcertViewModelFactory(
                object: FetchCallback{
                    override fun fetch(limit: Int?, eventIdOffset: String?, level: RequestParameters.RequestLevel, showLoader: Boolean) {
                        //Do the call to the server
                    }
                })
    
        viewModel = ViewModelProviders.of(this, factory).get(ConcertViewModel::class.java)
        adapter = ConcertAdapter()
        viewModel.concertList.observe(viewLifecycleOwner, Observer(adapter::submitList))
    

    这将是我的问题的答案,但是,请注意(至少目前)Android 分页库不处理可变数据。例如,如果音乐会有一个可以更改的乐队列表(例如,其中一个乐队无法参加音乐会),则该逻辑无法更新保存在数据库中的数据,因为它只在执行时向服务器请求额外的数据。到达存储在数据库中的数据的任一边界(顶部或底部)。因此,出于我自己的目的,这个库是无用的。

    【讨论】:

    • 我们在哪里使数据源失效?是在 callback.fetch 之后完成的吗?
    • 实现InvalidationTracker.Observer 类并覆盖方法onInvalidated 以侦听数据库更改并使数据源无效。将此 invalidationTracker 添加到 Room 数据库 invalidationTracker.addObserver 方法。参考InvalidationTracker教室图书馆。
    猜你喜欢
    • 2014-06-02
    • 2015-12-28
    • 2017-11-30
    • 1970-01-01
    • 1970-01-01
    • 2012-03-15
    • 1970-01-01
    • 1970-01-01
    • 2021-09-17
    相关资源
    最近更新 更多