0

我正在尝试使用分页库、MVVM 和 LiveData 实现一个无限列表。

在我的视图(在我的情况下是我的片段)中,我从 ViewModel 请求数据并观察变化:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel.getItems("someSearchQuery")
        viewModel.pagedItems.observe(this, Observer<PagedList<Item>> {
            // ItemPagedRecyclerAdapter
            // EDIT --> Found this in the official Google example
            // Workaround for an issue where RecyclerView incorrectly uses the loading / spinner
            // item added to the end of the list as an anchor during initial load.
            val layoutManager = (recycler.layoutManager as LinearLayoutManager)
            val position = layoutManager.findFirstCompletelyVisibleItemPosition()
            if (position != RecyclerView.NO_POSITION) {
                recycler.scrollToPosition(position)
            }
        })
}

在 ViewModel 中,我像这样获取我的数据:

private val queryLiveData = MutableLiveData<String>()

private val itemResult: LiveData<LiveData<PagedList<Item>>> = Transformations.map(queryLiveData) { query ->
    itemRepository.fetchItems(query)
}

val pagedItems: LiveData<PagedList<Item>> = Transformations.switchMap(itemResult) { it }

private fun getItems(queryString: String) {
    queryLiveData.postValue(queryString)
}

在存储库中,我通过以下方式获取数据:

fun fetchItems(query: String): LiveData<PagedList<Item>> {
    val boundaryCallback = ItemBoundaryCallback(query, this.accessToken!!, remoteDataSource, localDataSource)

    val dataSourceFactory = localDataSource.fetch(query)

    return dataSourceFactory.toLiveData(
        pageSize = Constants.PAGE_SIZE_ITEM_FETCH,
        boundaryCallback = boundaryCallback)
}

您可能已经注意到,我以 Google 的 Codelabs 为例,但遗憾的是我无法使其正常工作。

class ItemBoundaryCallback(
    private val query: String,
    private val accessToken: AccessToken,
    private val remoteDataSource: ItemRemoteDataSource,
    private val localDataSource: Item LocalDataSource
) : PagedList.BoundaryCallback<Item>() {

    private val executor = Executors.newSingleThreadExecutor()
    private val helper = PagingRequestHelper(executor)

    // keep the last requested page. When the request is successful, increment the page number.
    private var lastRequestedPage = 0

    private fun requestAndSaveData(query: String, helperCallback: PagingRequestHelper.Request.Callback) {
        val searchData = SomeSearchData()
        remoteDataSource.fetch Items(searchData, accessToken, lastRequestedPage * Constants.PAGE_SIZE_ITEMS_FETCH, { items ->
            executor.execute {
                localDataSource.insert(items) {
                    lastRequestedPage++
                    helperCallback.recordSuccess()
                }
            }
        }, { error ->
            helperCallback.recordFailure(Throwable(error))
        })
    }

    @MainThread
    override fun onZeroItemsLoaded() {
        helper.runIfNotRunning(PagingRequestHelper.RequestType.INITIAL) {
            requestAndSaveData(query, it)
        }
    }

    @MainThread
    override fun onItemAtEndLoaded(itemAtEnd: Item) {
        helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) {
            requestAndSaveData(query, it)
        }
    }

我的列表数据适配器:

class ItemPagedRecyclerAdapter : PagedListAdapter<Item, RecyclerView.ViewHolder>(ITEM_COMPARATOR) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return ItemViewHolder(parent)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position)
        if (item != null) {
            (holder as ItemViewHolder).bind(item, position)
        }
    }

    companion object {
        private val ITEM_COMPARATOR = object : DiffUtil.ItemCallback<Item>() {
            override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean =
                olItem.id == newItem.id

            override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean =
                oldItem == newItem
        }
    }
}

我现在的问题是:数据在本地获取并保存,甚至在我的列表中正确显示。但是数据似乎是“循环”的,因此尽管数据库中有不同的对象(我与 Stetho 核对过,大约有几百个),但总是显示相同的数据。奇怪的是,列表中的最后一项也总是相同的,有时滚动时会重新加载项目。另一个问题是它会在某个时候停止重新加载(有时是 200 个,有时是 300 个数据项)。

我认为这可能是因为我的 ITEM_COMPARATOR 检查错误并返回了错误的布尔值,所以我将两者都设置为return true只是为了测试,但这并没有改变。

我也在考虑向 LivePagedListBuilder 添加一个配置,但这也没有改变。所以我有点卡住了。我还研究了一些使用 PageKeyedDataSource 等执行此操作的示例,但 Google 的示例在没有它的情况下工作,所以我想知道为什么我的示例不起作用。 https://codelabs.developers.google.com/codelabs/android-paging/index.html?index=..%2F..index#5


编辑:

谷歌的蓝图中确实有另一个例子。我将它添加到代码中。https://github.com/android/architecture-components-samples/blob/master/PagingWithNetworkSample/app/src/main/java/com/android/example/paging/pagingwithnetwork/reddit/ui/RedditActivity.kt

现在它正在正确加载,但是当加载发生时,列表中的某些项目仍然翻转。


编辑2:

我编辑了 BoundaryCallback,但仍然无法正常工作(现在提供了 Google 建议的 PagingRequestHelper)。


编辑3:

我只用远程部分试了一下,效果很好。Room/room 提供的数据源似乎存在问题。

4

2 回答 2

5

好的,只是为了完成这个问题,我找到了解决方案。

要完成这项工作,您的后端/api 数据必须具有一致的列表顺序。翻转是由不断以不同于以前的顺序发送的数据引起的,因此使列表中的某些项目“翻转”。

因此,您必须在数据中保存一个附加字段(类似索引的字段)以相应地对数据进行排序。然后使用 ORDER BY 语句从 DAO 中的本地数据库获取数据。我希望我可以帮助那些忘记了和我一样的人:

@Query("SELECT * FROM items ORDER BY indexFromBackend")
abstract fun fetchItems(): DataSource.Factory<Int, Items>
于 2019-12-19T21:31:05.933 回答
0

您必须重写 PageKeyedDataSource 以使用 Paging 库实现分页逻辑。看看这个链接

于 2019-12-16T13:30:00.640 回答