分页库:跳转列表项+末尾始终相同的数据
Paging library: Jumping list items + always same data at the end
我正在尝试使用 Paging 库、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 中的代码实验室作为示例,但遗憾的是我无法使其正常工作。
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
}
}
}
我现在的问题是:数据是在本地获取和保存的,甚至在我的列表中也能正确显示。但是数据似乎是"looping",所以尽管数据库中有不同的对象,但总是显示相同的数据(我用Stetho查过,大约有几百个)。奇怪的是,列表中的最后一项也总是相同的,有时滚动时会重新加载项目。另一个问题是它在某个时候停止重新加载(有时是 200,有时是 300 个数据项)。
我想这可能是因为我的 ITEM_COMPARATOR 检查错误并返回了错误的布尔值,所以我将两者都设置为 return true
只是为了测试,但这并没有改变。
我也在考虑向 LivePagedListBuilder 添加配置,但这也没有任何改变。所以我有点卡住了。我还查看了一些使用 PageKeyedDataSource 等进行操作的示例,但是 Google 的示例在没有它的情况下也能正常工作,所以我想知道为什么我的示例不起作用。
https://codelabs.developers.google.com/codelabs/android-paging/index.html?index=..%2F..index#5
编辑:
Google 在他们的蓝图中确实有另一个例子。我将它添加到代码中。 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/the 数据源似乎有问题。
您必须覆盖 PageKeyedDataSource 才能使用 Paging 库实现分页逻辑。查看 this link
好的,为了完成这个问题,我找到了解决方案。
要完成这项工作,您的 backend/api-data 中的列表顺序必须一致。翻转是由于数据不断以不同于以前的顺序发送,因此使列表中的某些项目"flip"左右。
所以您必须为您的数据保存一个额外的字段(一个类似索引的字段)以相应地对您的数据进行排序。然后使用 ORDER BY 语句从 DAO 中的本地数据库中提取数据。我希望我可以帮助那些和我一样忘记的人:
@Query("SELECT * FROM items ORDER BY indexFromBackend")
abstract fun fetchItems(): DataSource.Factory<Int, Items>
我正在尝试使用 Paging 库、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 中的代码实验室作为示例,但遗憾的是我无法使其正常工作。
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
}
}
}
我现在的问题是:数据是在本地获取和保存的,甚至在我的列表中也能正确显示。但是数据似乎是"looping",所以尽管数据库中有不同的对象,但总是显示相同的数据(我用Stetho查过,大约有几百个)。奇怪的是,列表中的最后一项也总是相同的,有时滚动时会重新加载项目。另一个问题是它在某个时候停止重新加载(有时是 200,有时是 300 个数据项)。
我想这可能是因为我的 ITEM_COMPARATOR 检查错误并返回了错误的布尔值,所以我将两者都设置为 return true
只是为了测试,但这并没有改变。
我也在考虑向 LivePagedListBuilder 添加配置,但这也没有任何改变。所以我有点卡住了。我还查看了一些使用 PageKeyedDataSource 等进行操作的示例,但是 Google 的示例在没有它的情况下也能正常工作,所以我想知道为什么我的示例不起作用。 https://codelabs.developers.google.com/codelabs/android-paging/index.html?index=..%2F..index#5
编辑:
Google 在他们的蓝图中确实有另一个例子。我将它添加到代码中。 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/the 数据源似乎有问题。
您必须覆盖 PageKeyedDataSource 才能使用 Paging 库实现分页逻辑。查看 this link
好的,为了完成这个问题,我找到了解决方案。
要完成这项工作,您的 backend/api-data 中的列表顺序必须一致。翻转是由于数据不断以不同于以前的顺序发送,因此使列表中的某些项目"flip"左右。
所以您必须为您的数据保存一个额外的字段(一个类似索引的字段)以相应地对您的数据进行排序。然后使用 ORDER BY 语句从 DAO 中的本地数据库中提取数据。我希望我可以帮助那些和我一样忘记的人:
@Query("SELECT * FROM items ORDER BY indexFromBackend")
abstract fun fetchItems(): DataSource.Factory<Int, Items>