pager: do not skip next batch if current batch yields no valid apps

This commit is contained in:
Rahul Patel
2026-05-06 04:33:29 +05:30
parent 1484e0f24b
commit b2d10c17b4
6 changed files with 33 additions and 19 deletions

View File

@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2025 Aurora OSS
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.data
data class PageResult<T>(
val items: List<T>,
val hasMore: Boolean = items.isNotEmpty()
)

View File

@@ -10,6 +10,7 @@ import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
import androidx.paging.PagingState
import com.aurora.store.data.PageResult
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import com.aurora.store.data.paging.GenericPagingSource.Companion.pager
import kotlinx.coroutines.Dispatchers
@@ -23,7 +24,7 @@ import kotlinx.coroutines.withContext
* @param block Data to load into the pager
*/
class GenericPagingSource<T : Any>(
private val block: suspend (Int) -> List<T>
private val block: suspend (Int) -> PageResult<T>
) : PagingSource<Int, T>() {
companion object {
@@ -54,7 +55,7 @@ class GenericPagingSource<T : Any>(
fun <T : Any> manualPager(
pageSize: Int = DEFAULT_PAGE_SIZE,
enablePlaceholders: Boolean = true,
data: suspend (Int) -> List<T>
data: suspend (Int) -> PageResult<T>
): Pager<Int, T> = Pager(
config = PagingConfig(enablePlaceholders = enablePlaceholders, pageSize = pageSize),
pagingSourceFactory = { GenericPagingSource(data) }
@@ -65,12 +66,12 @@ class GenericPagingSource<T : Any>(
val page = params.key ?: 1
return try {
withContext(Dispatchers.IO) {
val data = block(page)
val result = block(page)
LoadResult.Page(
data = data,
data = result.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (data.isEmpty()) null else page + 1,
itemsAfter = if (data.isEmpty()) 0 else COUNT_UNDEFINED
nextKey = if (result.hasMore) page + 1 else null,
itemsAfter = if (result.hasMore) COUNT_UNDEFINED else 0
)
}
} catch (exception: Exception) {

View File

@@ -20,14 +20,13 @@
package com.aurora.store.viewmodel.all
import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aurora.extensions.TAG
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
import com.aurora.store.data.PageResult
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.util.PackageUtil
@@ -63,14 +62,11 @@ class InstalledViewModel @Inject constructor(
.chunked(20)
manualPager { page ->
try {
// page is 1-indexed, but list is 0-indexed
val chunk = pagedPackages.getOrNull(page - 1) ?: return@manualPager emptyList()
webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch apps", exception)
emptyList()
}
// page is 1-indexed, but list is 0-indexed
val chunk = pagedPackages.getOrNull(page - 1)
?: return@manualPager PageResult(emptyList<App>(), hasMore = false)
val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
PageResult(items, hasMore = page < pagedPackages.size)
}.flow.distinctUntilChanged()
.cachedIn(viewModelScope)
.onEach { _apps.value = it }

View File

@@ -28,6 +28,7 @@ import com.aurora.extensions.TAG
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.web.WebStreamHelper
import com.aurora.store.data.PageResult
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -61,7 +62,7 @@ class StreamBrowseViewModel @AssistedInject constructor(
var nextPageUrl: String = streamCluster.clusterNextPageUrl
manualPager { page ->
try {
val items = try {
when (page) {
1 -> streamCluster.clusterAppList
@@ -79,6 +80,7 @@ class StreamBrowseViewModel @AssistedInject constructor(
Log.e(TAG, "Failed to fetch apps for $page: $nextPageUrl", exception)
emptyList()
}
PageResult(items)
}.flow.distinctUntilChanged()
.cachedIn(viewModelScope)
.onEach { _apps.value = it }

View File

@@ -13,6 +13,7 @@ import androidx.paging.cachedIn
import com.aurora.extensions.TAG
import com.aurora.gplayapi.data.models.Review
import com.aurora.gplayapi.helpers.ReviewsHelper
import com.aurora.store.data.PageResult
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -46,7 +47,7 @@ class ReviewViewModel @AssistedInject constructor(
var reviewsNextPageUrl: String? = null
manualPager { page ->
try {
val items = try {
when (page) {
1 -> reviewsHelper.getReviews(packageName, filter).also {
reviewsNextPageUrl = it.nextPageUrl
@@ -66,6 +67,7 @@ class ReviewViewModel @AssistedInject constructor(
Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception)
emptyList()
}
PageResult(items)
}.flow.distinctUntilChanged()
.cachedIn(viewModelScope)
.onEach { _reviews.value = it }

View File

@@ -20,6 +20,7 @@ import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.SearchHelper
import com.aurora.gplayapi.helpers.contracts.SearchContract
import com.aurora.gplayapi.helpers.web.WebSearchHelper
import com.aurora.store.data.PageResult
import com.aurora.store.data.model.SearchFilter
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import com.aurora.store.data.providers.AuthProvider
@@ -80,7 +81,7 @@ class SearchViewModel @Inject constructor(
}.distinctBy { app -> app.packageName }
manualPager { page ->
try {
val items = try {
when (page) {
1 -> contract.searchResults(query)
.also { nextBundleUrl = it.streamNextPageUrl }
@@ -110,6 +111,7 @@ class SearchViewModel @Inject constructor(
Log.e(TAG, "Failed to search results for $query", exception)
emptyList()
}
PageResult(items)
}.flow.distinctUntilChanged()
.cachedIn(viewModelScope)
.onEach { _apps.value = it }