RecyclerView 与 Paging Library 和 PositionalDataSource 保持为空
Posted
技术标签:
【中文标题】RecyclerView 与 Paging Library 和 PositionalDataSource 保持为空【英文标题】:RecyclerView remains empty with Paging Library and PositionalDataSource 【发布时间】:2020-05-26 00:21:46 【问题描述】:我正在尝试在我的项目中配置 android Paging 库以将分页消息列表加载到 RecyclerView 中。由于我的 API 使用偏移量和最大值,因此我使用的是 PositionalDataSource。
这是我的 DataSource 实现,其中 DataStore 使用 RetroFit 加载消息,我可以在控制台中看到消息正在正确加载,并转换为 MessageListItem 的实例:
class MessageDataSource: PositionalDataSource<MessageListItem>()
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<MessageListItem>)
DataStore.shared.loadMessages(params.startPosition, params.loadSize) result, error ->
if(result != null)
callback.onResult(result.items)
else
callback.onError(MessageDataSourceException(error))
override fun loadInitial(
params: LoadInitialParams,
callback: LoadInitialCallback<MessageListItem>
)
DataStore.shared.loadMessages(params.requestedStartPosition, params.requestedLoadSize) response, error ->
if(response != null)
callback.onResult(response.items, response.offset, response.total)
else
callback.onError(MessageDataSourceException(error))
class MessageDataSourceException(rootCause: Throwable? = null): Exception(rootCause)
这是我的 DataSourceFactory 实现:
class MessageDataSourceFactory: DataSource.Factory<Int, MessageListItem>()
val messageLiveDataSource = MutableLiveData<MessageDataSource>()
private lateinit var messageDataSource: MessageDataSource
override fun create(): DataSource<Int, MessageListItem>
messageDataSource = MessageDataSource()
messageLiveDataSource.postValue(messageDataSource)
return messageDataSource
这是我的 MessageListAdapter 实现:
object MessageListItemDiff: DiffUtil.ItemCallback<MessageListItem>()
override fun areItemsTheSame(oldItem: MessageListItem, newItem: MessageListItem): Boolean
return oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: MessageListItem, newItem: MessageListItem): Boolean
return oldItem == newItem
class MessageListAdapter(private val clickListener: View.OnClickListener):
PagedListAdapter<MessageListItem, MessageListAdapter.MessageHolder>(MessageListItemDiff)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageHolder
val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.item_message, parent, false)
return MessageHolder(inflatedView, clickListener)
override fun onBindViewHolder(holder: MessageHolder, position: Int)
holder.bind(getItem(position)!!)
class MessageHolder(itemView: View, private val clickListener: View.OnClickListener) : RecyclerView.ViewHolder(itemView)
val unreadIndicator = itemView.findViewById<ImageView>(R.id.unreadIndicator)
val title = itemView.findViewById<TextView>(R.id.title)
val dateSent = itemView.findViewById<TextView>(R.id.dateSent)
val cardView = itemView.findViewById<CardView>(R.id.card_view)
fun bind(message: MessageListItem)
cardView.tag = message
cardView.setOnClickListener(clickListener)
title.text = message.title
dateSent.text = TimeAgo.using(message.dateSent.time)
if(message.isRead)
unreadIndicator.setImageResource(0)
else
unreadIndicator.setImageResource(R.drawable.ic_unread)
最后是我的 ViewModel:
class MessageListViewModel: ViewModel()
val messagePagedList: LiveData<PagedList<MessageListItem>>
val liveDataSource: LiveData<MessageDataSource>
init
val messageDataSourceFactory = MessageDataSourceFactory()
liveDataSource = messageDataSourceFactory.messageLiveDataSource
val pagedListConfig = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(30)
.setPrefetchDistance(90)
.build()
messagePagedList = LivePagedListBuilder(messageDataSourceFactory, pagedListConfig).build()
这是片段中的 onViewCreated 实现,它应该显示名为 messageList 的回收器视图:
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
messageList.layoutManager = LinearLayoutManager(context!!)
messageList.setHasFixedSize(true)
messageListViewModel = ViewModelProvider(this).get(MessageListViewModel::class.java)
messageListAdapter = MessageListAdapter(this)
messageListViewModel.messagePagedList.observe(this, Observer messages ->
messageListAdapter.submitList(messages)
)
messageList.adapter = messageListAdapter
问题是我可以看到数据正在从服务器加载,但它从未到达回收站视图。如果我在观察者行 (messageListAdapter.submitList(messages)
) 上添加断点,我会收到一个带有空消息列表的调用,仅此而已。
我不得不承认我对所有这些类以及它们应该做什么感到很困惑,这是我在 Android 中的第一个分页实现,我不得不调整我在这里和那里找到的代码,因为我不想要使用 Room 数据库、RxJava 或 PageKeyedDataSource,大多数示例都是这样做的。
知道会发生什么吗?
【问题讨论】:
我看不到任何调用 loadRange() 或 loadInital() 的代码。此外,您只在此处发布的 sn-ps 中调用 postValue() 一次(创建数据源时,所以我想那时不会显示任何值)。所以很难说哪里少了一些东西 loadRange 和 loadInitial 应该在 Paging 库中调用。 postValue 也一样。 你能给recycleview一个固定大小的高度吗,有时我对recycleview设置了一些约束,加载时它没有显示出来。也可以尝试将线性管理器更改为此 LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); 回收器视图在一个ConstraintLayout中,在所有方面都被限制在父级上。而且 LinearLayoutManager 必须是垂直的。我认为这与 recyclerview 本身没有任何关系。 您正在代码中使用 callback.onError() 调用。当前版本的分页库 (2.1.1) 中没有适当的错误处理。最近添加了此方法,但尚未在任何地方记录,并且在许多情况下都不起作用,包括使用 PositionalDataSource 时。所以你必须忽略错误或实现自己的重试机制。你能在任何地方放置断点并确认在 loadInitial() 中使用正确的参数(非空列表和正确的偏移量)调用了 callback.onResult() 并且没有调用 callback.onError() 吗? 【参考方案1】:据我所知,为了使PagedList
实例能够正常工作,必须在LiveData
调度它后立即预加载初始数据。为此,需要在loadInitial()
方法返回时加载数据,这意味着您需要同步执行网络调用,并从loadInitial()
方法调用中调用callback.onResult()
在方法返回之前,而不是使用回调。在那里同步执行网络调用是安全的,因为LivePagedListBuilder
将负责从后台线程调用PagedList.Builder()
。
此外,此时错误处理实现几乎没有记录且不完整(在 2.1.1 版中),因此在许多情况下,对最近添加的 callback.onError()
方法的调用将失败。例如,在 2.1.1 版本中,TiledPagedList
中根本没有实现错误处理,这是用于 PositionalDataSource
的 PagedList
的类型。
最后,如果您在loadInitial()
中返回列表的确切大小(如您在此处所做的那样),那么在loadRange()
中,您需要确保始终准确返回所请求的项目数。如果 API 请求 30 个项目,而您只返回 20 个,则您的应用可能会崩溃。我发现的一种解决方法是您可以使用 null
值填充结果列表,以便它始终具有请求的大小,但是您需要启用占位符。或者,不要在 loadInitial() 中返回确切的大小,列表只会动态增长。
此 API 复杂且难以使用,因此请不要责怪自己。 Google 目前正在开发用 Kotlin 编写的新版本 3.0,有望解决旧版本的所有问题。
【讨论】:
我正在使用 rxjava 来提取数据。试图将我的数据检索更改为在 loadiinital 内部阻塞 - 顺便说一句,没有区别。【参考方案2】:改变这个:
messageListViewModel.messagePagedList.observe(this, Observer messages ->
messageListAdapter.submitList(messages)
)
用这个:
messageListViewModel.messagePagedList.observe(viewLifeCycleOwner, PagedList(messageListAdapter::submitList))
来源:https://developer.android.com/topic/libraries/architecture/paging#ex-observe-livedata
【讨论】:
我在 submitList 上收到一个错误,因为重载解析不明确(它不知道要使用 submitList 的 3 个重载版本中的哪一个),然后在 PagedList 构造函数调用上出现另一个错误,但我猜猜这是第一个的结果。 如果将PagedList(messageListAdapter::submitList)
更改为Observer(messageListAdapter::submitList)
,会出现同样的错误吗?
没有更多的编译错误,因为它几乎完成了我在原始代码中的工作,但结果没有到达 RecyclerView,即使我可以看到它们到达 DataSource
实际上我从您的代码中注意到一件事:我没有看到任何 suspend
函数或 Observable
类型正在处理。您是否正确处理后台处理?因为如果不这样做,您将观察到一个空列表并将其设置到您的适配器,然后再从 HTTP 请求中获得任何结果。以上是关于RecyclerView 与 Paging Library 和 PositionalDataSource 保持为空的主要内容,如果未能解决你的问题,请参考以下文章
基于Android官方Paging Library的RecyclerView分页加载框架
Android Paging 3 在 invalidate() 上清除 recyclerview
Android-利用Jetpack-Compose-+Paging3+swiperefresh实现分页加载,下拉上拉效果
Android Paging 库不会触发 loadAfter()