RecyclerView остается пустым с библиотекой подкачки и PositionalDataSource

Я пытаюсь настроить библиотеку Android Paging в своем проекте для загрузки разбитого на страницы списка сообщений в RecyclerView. Поскольку мой API использует offset и max, я использую 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, и мне пришлось адаптировать код, который я нашел здесь и там, потому что я не хотел использовать База данных комнаты, RxJava или PageKeyedDataSource, что и используется в большинстве примеров.

Есть идеи, что может происходить?


person Sebastien    schedule 10.02.2020    source источник
comment
Я не вижу кода, в котором вы вызываете loadRange () или loadInital (). Кроме того, вы вызываете postValue () только один раз в размещенных здесь фрагментах (когда создается источник данных, поэтому я полагаю, что тогда не будет никаких значений для отображения). Так что сложно сказать, где чего-то не хватает   -  person Bö macht Blau    schedule 10.02.2020
comment
loadRange и loadInitial должны вызываться внутри библиотеки Paging. То же самое и для postValue.   -  person Sebastien    schedule 10.02.2020
comment
Можете ли вы указать высоту фиксированного размера для recycleview, иногда я накладывал некоторые ограничения на recycleview, и он не отображался при загрузке. Также попробуйте изменить linearmanager на этот LinearLayoutManager (this, LinearLayoutManager.HORIZONTAL, false);   -  person DemoDemo    schedule 10.02.2020
comment
Представление ресайклера находится в ConstraintLayout, ограниченном родительским со всех сторон. И LinearLayoutManager должен быть вертикальным. Я не думаю, что это как-то связано с самим recyclerview.   -  person Sebastien    schedule 10.02.2020
comment
Вы используете в своем коде вызовы callback.onError (). В текущей версии библиотеки подкачки (2.1.1) нет надлежащей обработки ошибок. Этот метод был добавлен недавно, но он еще нигде не задокументирован и не работает во многих случаях, в том числе при использовании PositionalDataSource. Поэтому вам нужно игнорировать ошибки или реализовать свой собственный механизм повтора. Можете ли вы поставить точки останова повсюду и подтвердить, что callback.onResult () вызывается в loadInitial () с правильными аргументами (непустой список и правильное смещение) и что callback.onError () не вызывается?   -  person BladeCoder    schedule 20.02.2020


Ответы (2)


Насколько я знаю, для того, чтобы все работало правильно, экземпляр PagedList должен быть предварительно загружен исходными данными, как только он будет отправлен LiveData. Чтобы это произошло, данные должны быть загружены при возврате метода loadInitial(), а это означает, что вам необходимо выполнить сетевой вызов синхронно и вызвать callback.onResult() из вызова метода loadInitial() до того, как метод вернется. использования обратного вызова. Здесь безопасно выполнять сетевые вызовы синхронно, потому что LivePagedListBuilder позаботится о вызове PagedList.Builder() из фонового потока.

Кроме того, реализация обработки ошибок в значительной степени недокументирована и неполна на этом этапе (в версии 2.1.1), поэтому вызовы недавно добавленного метода callback.onError() во многих случаях завершатся ошибкой. Например, в версии 2.1.1 обработка ошибок вообще не реализована в TiledPagedList, который является типом PagedList, используемым для PositionalDataSource.

Наконец, если вы возвращаете точный размер для списка в loadInitial() (как здесь), то в loadRange() вам нужно убедиться, что вы всегда возвращаете именно то количество элементов, которое запрашивается. Если API запрашивает 30 элементов, а вы возвращаете только 20, ваше приложение может аварийно завершить работу. Я обнаружил один обходной путь: вы можете дополнить список результатов null значениями, чтобы он всегда имел запрошенный размер, но тогда вам нужно включить заполнители. В качестве альтернативы, не возвращайте точный размер в loadInitial (), и список будет просто динамически расти.

Этот API сложен и сложен в использовании, поэтому не вините себя. В настоящее время Google работает над новой версией 3.0, написанной на Kotlin, которая, надеюсь, исправит все проблемы старой версии.

person BladeCoder    schedule 20.02.2020
comment
Я использую rxjava для извлечения данных. Пытался изменить получение данных на блокировку внутри loadiinital - кстати, без разницы. - person ror; 21.05.2020

Измените это:

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

person Harry Timothy    schedule 16.02.2020
comment
Я получаю сообщение об ошибке в submitList из-за неоднозначности разрешения перегрузки (он не знает, какую из 3 перегруженных версий submitList использовать), а затем еще одна ошибка при вызове конструктора PagedList, но я предполагаю, что это следствие первый. - person Sebastien; 16.02.2020
comment
Если вы измените PagedList(messageListAdapter::submitList) на Observer(messageListAdapter::submitList), вы получите ту же ошибку? - person Harry Timothy; 16.02.2020
comment
Больше нет ошибок компиляции, так как он делает почти то же, что и у меня в исходном коде, но результаты не доходят до RecyclerView, хотя я вижу, как они достигают DataSource - person Sebastien; 16.02.2020
comment
На самом деле я замечаю одну вещь в вашем коде: я не вижу обрабатываемых suspend функций или Observable типов. Правильно ли вы справляетесь с фоновой обработкой? Потому что, если вы этого не сделаете, вы увидите пустой список и установите его для своего адаптера, прежде чем получите какой-либо результат от HTTP-запроса. - person Harry Timothy; 17.02.2020