Каждый раз, когда вызывается invalidate()
, весь список будет считаться недействительным и снова будет полностью скомпилирован, создавая новый экземпляр DataSource . На самом деле это ожидаемое поведение, но давайте посмотрим на пошаговую последовательность событий, происходящих под капотом, чтобы понять проблему:
- A DataSource экземпляр создан, и его Вызван метод
loadInitial
с нулевыми элементами (поскольку данные еще не сохранены) - BoundaryCallback '
onZeroItemsLoaded
будет вызван, поэтому первые данные будут извлечены, сохранены и наконец, он сделает список недействительным, поэтому он будет создан снова. - Будет создан новый экземпляр DataSource , который снова вызовет
loadInitial
, но на этот раз, поскольку он уже есть некоторые данные, он будет извлекать эти ранее сохраненные элементы. - Пользователь прокрутит до конца списка, поэтому будет попытаться загрузить новую страницу из DataSource , вызвав
loadAfter
, который получит 0 элементов, поскольку больше нет элементов для загрузки. - Так будет вызван
onItemAtEndLoaded
в BoundaryCallback , извлечение второй страницы, сохранение новых элементов и, наконец, недействительность Снова весь список. - Опять же, будет создан новый Источник данных , еще раз вызвав его
loadInitial
, который будет получать только первые элементы страницы. - После повторного вызова
loadAfter
он сможет извлекать новые элементы страницы, как только что они были добавлены. - Это будет go для каждой страницы.
Проблему здесь можно определить по Шаг 6 .
Дело в том, что каждый раз, когда мы аннулируем DataSource , его loadInitial
будет извлекать только первые элементы страницы. Несмотря на то, что все остальные элементы страниц уже сохранены, новый список не будет знать об их существовании, пока не будет вызван соответствующий им loadAfter
. Таким образом, после извлечения новой страницы, сохранения их элементов и аннулирования списка наступит момент, когда новый список будет составлен только из элементов первой страницы (так как loadInitial
будет только извлекать их). Этот новый список будет отправлен на Adapter , поэтому RecyclerView будет отображать только первые элементы страницы, создавая впечатление, что он снова перепрыгнул на первый элемент. Однако реальность такова, что все остальные элементы были удалены, так как теоретически их больше нет в списке. После этого, как только пользователь прокрутит вниз, будет вызван соответствующий loadAfter
, и элементы страницы будут снова извлечены из сохраненных, пока не будет нажата новая страница без сохраненных элементов, что снова сделает недействительным весь список после хранения новых элементов.
Итак, чтобы избежать этого, хитрость заключается в том, чтобы loadInitial
не только всегда извлекал элементы первой страницы, но и все уже загруженные элементы . Таким образом, когда страница становится недействительной и вызывается новый DataSource loadInitial
, новый список больше не будет состоять только из первых элементов страницы, а из всех уже загруженных, поэтому что они не удалены из RecyclerView .
. Для этого мы можем отслеживать, сколько страниц уже загружено, чтобы мы могли сообщать каждому новому источникам данных сколько их должно быть получено в loadInitial
.
Простое решение состоит в создании класса для отслеживания текущей страницы:
class PageTracker {
var currentPage = 0
}
Затем измените пользовательский DataSource , чтобы получить экземпляр этого класса, и обновите его:
class ColorsDataSource(
private val pageTracker: PageTracker
private val colorsRepository: ColorsRepository
) : PageKeyedDataSource<Int, ColorEntity>() {
override fun loadInitial(
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, ColorEntity>
) {
//...
val alreadyLoadedItems = (pageTracker.currentPage + 1) * params.requestedLoadSize
val resultFromDB = colorsRepository.getColors(0, alreadyLoadedItems)
callback.onResult(resultFromDB, null, pageTracker.currentPage + 1)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, ColorEntity>) {
pageTracker.currentPage = params.key
//...
}
//...
}
Наконец, создайте экземпляр PageTracker
и передайте его каждому новому Источник данных экземпляр
dataSourceFactory = object : DataSource.Factory<Int, ColorEntity>() {
val pageTracker = PageTracker()
override fun create(): DataSource<Int, ColorEntity> {
dataSource = ColorsDataSource(pageTracker, repository)
return dataSource
}
}
ПРИМЕЧАНИЕ 1
Важно отметить, что если необходимо повторно обновить sh весь список снова (из-за действия pull-to-refre sh или чего-либо еще), экземпляр PageTracker
перед аннулированием списка потребуется обновить его до currentPage = 0
.
ПРИМЕЧАНИЕ 2
Также важно отметить, что этот подход обычно не требуется при использовании Room , так как в этом случае нам, вероятно, не нужно создавать наш пользовательский DataSource , а вместо этого сделать Dao напрямую возвращает DataSource.Factory непосредственно из запроса. Затем, когда мы получим новые данные из-за BoundaryCallback вызовов и сохраняем элементы, Room автоматически обновит наш список с всеми элементами.