使用Paging Library和PositionalDataSource,RecyclerView仍然为空。

6

我正在尝试在我的项目中配置Android分页库,以将分页信息列表加载到RecyclerView中。由于我的API使用偏移量和最大值,所以我正在使用PositionalDataSource。

这是我的数据源实现,其中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()
    }
}

这里是在用于显示RecyclerView的碎片中的onViewCreated实现:

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()或loadInitial()。此外,您在此处发布的片段中只调用了一次postValue()(在创建Datasource时),因此我想那时可能没有任何值可显示。因此很难确定哪里缺少某些内容。 - Bö macht Blau
loadRange和loadInitial应该在Paging库内部调用。postValue也是如此。 - Sebastien
你能给RecycleView一个固定的高度吗?有时我会在RecycleView中设置一些限制,但当加载时它并没有显示出来。另外,也可以尝试将LinearManager更改为这个LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)。 - DemoDemo
回收视图位于ConstraintLayout中,四周都受到父级的限制。LinearLayoutManager必须是垂直的。我认为这与recyclerview本身无关。 - Sebastien
你的代码中使用了callback.onError()调用。当前版本的分页库(2.1.1)没有适当的错误处理。虽然最近添加了这种方法,但它还没有在任何地方得到记录,并且在许多情况下都无法正常工作,包括使用PositionalDataSource时。因此,您必须忽略错误或实现自己的重试机制。您能否在loadInitial()中的每个位置设置断点,并确认callback.onResult()是否以正确的参数(非空列表和正确的偏移量)被调用,而callback.onError()没有被调用? - BladeCoder
2个回答

3
据我所知,为了使所有内容正常工作,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中 - 没有任何区别。 - ror

0

我在submitList上遇到了一个错误,因为存在重载分辨模糊(它不知道要使用submitList的3个重载版本中的哪一个),然后在调用PagedList构造函数时又遇到了另一个错误,但我想这个错误是第一个错误的结果。 - Sebastien
如果您将 PagedList(messageListAdapter::submitList) 更改为 Observer(messageListAdapter::submitList),您是否会得到相同的错误? - Harry Timothy
没有更多的编译错误,因为它几乎做了我原来代码中的所有事情,但结果没有到达RecyclerView,尽管我可以看到它们到达了数据源。 - Sebastien
实际上,从你的代码中我注意到一件事:我没有看到任何suspend函数或Observable类型被处理。你是否正确地处理了后台处理?因为如果你没有,你将会在收到HTTP请求结果之前观察到一个空列表并将其设置到你的适配器中。 - Harry Timothy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接