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 中根本没有实现错误处理,这是用于 PositionalDataSourcePagedList 的类型。

最后,如果您在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()

Android jetpack的Paging和Room结合使用

Android Paging 3 不显示 Loadstate Adapter