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

View File

@@ -20,14 +20,13 @@
package com.aurora.store.viewmodel.all package com.aurora.store.viewmodel.all
import android.content.Context import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aurora.extensions.TAG
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper 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.paging.GenericPagingSource.Companion.manualPager
import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.util.PackageUtil import com.aurora.store.util.PackageUtil
@@ -63,14 +62,11 @@ class InstalledViewModel @Inject constructor(
.chunked(20) .chunked(20)
manualPager { page -> manualPager { page ->
try { // page is 1-indexed, but list is 0-indexed
// page is 1-indexed, but list is 0-indexed val chunk = pagedPackages.getOrNull(page - 1)
val chunk = pagedPackages.getOrNull(page - 1) ?: return@manualPager emptyList() ?: return@manualPager PageResult(emptyList<App>(), hasMore = false)
webAppDetailsHelper.getAppDetails(chunk.map { it.packageName }) val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
} catch (exception: Exception) { PageResult(items, hasMore = page < pagedPackages.size)
Log.e(TAG, "Failed to fetch apps", exception)
emptyList()
}
}.flow.distinctUntilChanged() }.flow.distinctUntilChanged()
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
.onEach { _apps.value = it } .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.App
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.web.WebStreamHelper import com.aurora.gplayapi.helpers.web.WebStreamHelper
import com.aurora.store.data.PageResult
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@@ -61,7 +62,7 @@ class StreamBrowseViewModel @AssistedInject constructor(
var nextPageUrl: String = streamCluster.clusterNextPageUrl var nextPageUrl: String = streamCluster.clusterNextPageUrl
manualPager { page -> manualPager { page ->
try { val items = try {
when (page) { when (page) {
1 -> streamCluster.clusterAppList 1 -> streamCluster.clusterAppList
@@ -79,6 +80,7 @@ class StreamBrowseViewModel @AssistedInject constructor(
Log.e(TAG, "Failed to fetch apps for $page: $nextPageUrl", exception) Log.e(TAG, "Failed to fetch apps for $page: $nextPageUrl", exception)
emptyList() emptyList()
} }
PageResult(items)
}.flow.distinctUntilChanged() }.flow.distinctUntilChanged()
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
.onEach { _apps.value = it } .onEach { _apps.value = it }

View File

@@ -13,6 +13,7 @@ import androidx.paging.cachedIn
import com.aurora.extensions.TAG import com.aurora.extensions.TAG
import com.aurora.gplayapi.data.models.Review import com.aurora.gplayapi.data.models.Review
import com.aurora.gplayapi.helpers.ReviewsHelper import com.aurora.gplayapi.helpers.ReviewsHelper
import com.aurora.store.data.PageResult
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@@ -46,7 +47,7 @@ class ReviewViewModel @AssistedInject constructor(
var reviewsNextPageUrl: String? = null var reviewsNextPageUrl: String? = null
manualPager { page -> manualPager { page ->
try { val items = try {
when (page) { when (page) {
1 -> reviewsHelper.getReviews(packageName, filter).also { 1 -> reviewsHelper.getReviews(packageName, filter).also {
reviewsNextPageUrl = it.nextPageUrl reviewsNextPageUrl = it.nextPageUrl
@@ -66,6 +67,7 @@ class ReviewViewModel @AssistedInject constructor(
Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception) Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception)
emptyList() emptyList()
} }
PageResult(items)
}.flow.distinctUntilChanged() }.flow.distinctUntilChanged()
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
.onEach { _reviews.value = it } .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.SearchHelper
import com.aurora.gplayapi.helpers.contracts.SearchContract import com.aurora.gplayapi.helpers.contracts.SearchContract
import com.aurora.gplayapi.helpers.web.WebSearchHelper 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.model.SearchFilter
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
@@ -80,7 +81,7 @@ class SearchViewModel @Inject constructor(
}.distinctBy { app -> app.packageName } }.distinctBy { app -> app.packageName }
manualPager { page -> manualPager { page ->
try { val items = try {
when (page) { when (page) {
1 -> contract.searchResults(query) 1 -> contract.searchResults(query)
.also { nextBundleUrl = it.streamNextPageUrl } .also { nextBundleUrl = it.streamNextPageUrl }
@@ -110,6 +111,7 @@ class SearchViewModel @Inject constructor(
Log.e(TAG, "Failed to search results for $query", exception) Log.e(TAG, "Failed to search results for $query", exception)
emptyList() emptyList()
} }
PageResult(items)
}.flow.distinctUntilChanged() }.flow.distinctUntilChanged()
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
.onEach { _apps.value = it } .onEach { _apps.value = it }