6

我正在尝试在我的项目中配置 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,大多数示例都是这样做的。

知道会发生什么吗?

4

2 回答 2

3

据我所知,为了让一切正常工作,PagedList实例必须在LiveData. 为此,需要在loadInitial()方法返回时加载数据,这意味着您需要同步执行网络调用,并在方法返回之前callback.onResult()loadInitial()方法调用内部调用,而不是使用回调。在那里同步执行网络调用是安全的,因为LivePagedListBuilder它将负责PagedList.Builder()从后台线程调用。

此外,此时错误处理实现几乎没有记录且不完整(在 2.1.1 版中),因此callback.onError()在许多情况下对最近添加的方法的调用将失败。例如,在 2.1.1 版本中,根本没有实现错误处理TiledPagedList,这是PagedList用于 a的类型PositionalDataSource

最后,如果您返回列表的确切大小loadInitial()(如您在此处所做的那样),那么loadRange()您需要确保始终返回所请求的项目数量。如果 API 请求 30 个项目,而您只返回 20 个,则您的应用可能会崩溃。我发现的一种解决方法是您可以使用null值填充结果列表,使其始终具有请求的大小,但是您需要启用占位符。或者,不要在 loadInitial() 中返回确切的大小,列表只会动态增长。

这个 API 使用起来很复杂而且很棘手,所以不要责怪自己。谷歌目前正在开发一个用 Kotlin 编写的新版本 3.0,它有望解决旧版本的所有问题。

于 2020-02-20T12:00:25.887 回答
0

改变这个:

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

于 2020-02-16T03:22:57.237 回答