Я пытаюсь реализовать пейджинговую логику c с частичной загрузкой данных на одну страницу. У меня есть страница с максимальным значением page_size 100, но мне не нужны все 100 значений для одного запроса, мне нужно запросить 30 записей в одном запросе, 30 в секунду, 30 в третьем 10 (из-за макс. 100) в четвертом запрос и после этого я меняю номер страницы. Вот как я написал это сейчас: Класс источника данных
class NewsDataSource(
private val compositeDisposable: CompositeDisposable,
private val newsRequests: NewsRequests
) : PageKeyedDataSource<PageNewsKey, News.Data?>() {
private val pRequestNewsStatuses = MutableLiveData<LiveDataStatuses>(LiveDataStatuses.IDLE)
val requestNewsStatuses: LiveData<LiveDataStatuses> = pRequestNewsStatuses
override fun loadInitial(
params: LoadInitialParams<PageNewsKey>,
callback: LoadInitialCallback<PageNewsKey, News.Data?>
) {
val pageNewsKey = PageNewsKey()
pRequestNewsStatuses.postValue(LiveDataStatuses.WAITING)
compositeDisposable.add(
newsRequests.getNews(pageNewsKey.newsCount, pageNewsKey.page)
.subscribeOn(Schedulers.io())
.map {
it.data ?: emptyList()
}
.subscribe({
callback.onResult(it, null, pageNewsKey.nextPageKey)
}, {
})
)
}
override fun loadBefore(
params: LoadParams<PageNewsKey>,
callback: LoadCallback<PageNewsKey, News.Data?>
) {
}
override fun loadAfter(
params: LoadParams<PageNewsKey>,
callback: LoadCallback<PageNewsKey, News.Data?>
) {
pRequestNewsStatuses.postValue(LiveDataStatuses.WAITING)
compositeDisposable.add(
newsRequests.getNews(params.key.newsCount, params.key.page)
.subscribeOn(Schedulers.io())
.map {
it.data ?: emptyList()
}
.subscribe({
callback.onResult(it, params.key.nextPageKey)
}, {
})
)
}
Класс ключа:
class PageNewsKey {
var newsCount: Int = NEWS_COUNT_DEFAULT_VALUE
var page: Int = 1
val nextPageKey: PageNewsKey
get() {
return iterate()
}
private fun iterate(): PageNewsKey {
if (newsCount == NEWS_COUNT_MAX_VALUE) {
newsCount = NEWS_COUNT_DEFAULT_VALUE
page = page.inc()
} else {
val newNewsCount = newsCount + NEWS_COUNT_DEFAULT_VALUE
newsCount = if (newNewsCount > NEWS_COUNT_MAX_VALUE) {
NEWS_COUNT_MAX_VALUE
} else {
newNewsCount
}
}
return this
}
}
Фабрика источника данных:
class NewsDataSourceFactory(
private val compositeDisposable: CompositeDisposable,
private val newsRequests: NewsRequests
) : DataSource.Factory<PageNewsKey, News.Data?>() {
private val pNewsDataSource = MutableLiveData<NewsDataSource?>(null)
val newsDataSource: LiveData<NewsDataSource?> = pNewsDataSource
override fun create(): DataSource<PageNewsKey, News.Data?> {
val newsDataSource = NewsDataSource(compositeDisposable, newsRequests)
pNewsDataSource.postValue(newsDataSource)
return newsDataSource
}
fun refresh() {
pNewsDataSource.value?.invalidate()
}
}
Этот код находится в модели представления класс:
private val newsDataSourceFactory = NewsDataSourceFactory(compositeDisposable, NewsRequests.getNewsRequest())
private val newsDataSourceFactoryConfig = PagedList.Config.Builder()
.setEnablePlaceholders(true)
.setPageSize(NEWS_COUNT_DEFAULT_VALUE)
.build()
val news: LiveData<PagedList<News.Data?>> = LivePagedListBuilder(newsDataSourceFactory, newsDataSourceFactoryConfig).build()
Наблюдатель данных в реальном времени внутри нужного фрагмента:
homeViewModel.news.observe(this, Observer {
it?.let {
newsAdapter.submitList(it)
}
})
Адаптер:
class NewsAdapter : PagedListAdapter<News.Data, NewsAdapter.ViewHolder>(News.Data.NEWS_DATA_DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.news_row, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bingView(getItem(position))
}
fun getItemAtPosition(position: Int): News.Data? = getItem(position)
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var iv_news_image: ImageView? = null
private var tv_news_title: TextView? = null
private var tv_news_subtitle: TextView? = null
private var clpb_news_image: ContentLoadingProgressBar? = null
init {
iv_news_image = itemView.findViewById(R.id.iv_news_image)
tv_news_title = itemView.findViewById(R.id.tv_news_title)
tv_news_subtitle = itemView.findViewById(R.id.tv_news_subtitle)
clpb_news_image = itemView.findViewById(R.id.clpb_news_image)
clpb_news_image?.hide()
}
fun bingView(data: News.Data?) {
iv_news_image?.let {
clpb_news_image?.show()
GlideApp.with(itemView.context)
.load(data?.imageUrl)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.transition(DrawableTransitionOptions.withCrossFade(500))
.listener(object : IDoAfterTerminateGlide {
override fun doAfterTerminate() {
clpb_news_image?.hide()
}
})
.dontAnimate()
.into(it)
}
tv_news_title?.text = data?.title
tv_news_subtitle?.text = data?.subtitle
}
}
}
И, наконец, класс данных с обратным вызовом diff:
data class News(
@SerializedName("current_page")
val currentPage: Int? = null,
@SerializedName("data")
val data: List<Data?>? = null,
@SerializedName("first_page_url")
val firstPageUrl: String? = null,
@SerializedName("from")
val from: Int? = null,
@SerializedName("last_page")
val lastPage: Int? = null,
@SerializedName("last_page_url")
val lastPageUrl: String? = null,
@SerializedName("next_page_url")
val nextPageUrl: String? = null,
@SerializedName("path")
val path: String? = null,
@SerializedName("per_page")
val perPage: String? = null,
@SerializedName("prev_page_url")
val prevPageUrl: String? = null,
@SerializedName("to")
val to: Int? = null,
@SerializedName("total")
val total: Int? = null
) {
data class Data(
@SerializedName("body")
val body: String? = null,
@SerializedName("date")
val date: String? = null,
@SerializedName("image_url")
val imageUrl: String? = null,
@SerializedName("news_id")
val newsId: String? = null,
@SerializedName("subtitle")
val subtitle: String? = null,
@SerializedName("title")
val title: String? = null
) {
companion object {
val NEWS_DATA_DIFF_CALLBACK = object : DiffUtil.ItemCallback<Data>() {
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.newsId == newItem.newsId
}
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.body == newItem.body && oldItem.date == oldItem.date
&& oldItem.imageUrl == newItem.imageUrl
&& oldItem.subtitle == newItem.subtitle
&& oldItem.title == newItem.title
}
}
}
}
}
json ответ И эта настройка работает, как я ожидал, она делает запросы:
- www.example.com?page=1&page_size=30
- www.example.com?page=1&page_size=60
- www.example.com?page=1&page_size=90
- www.example.com?page=1&page_size=10
- www.example.com?page=2&page_size=30
Но проблема в том, что обратный вызов элемента diff не делает его работу Я ожидаю, и я получаю дублированные данные. Итак, мой вопрос довольно прост, предоставляет ли android источник данных с ключами страницы такую функциональность?