Rework internals of ApiRequestPager (#518)

## Description
Changes how `init()` works for `ApiRequestPager` so that it 1) always
forces updating the total count if needed and 2) propagates errors
during the initial fetch up to the original caller

This means if an error occurs during the initialization of the pager
data, it won't be quietly logged and ignored. Instead, ViewModel code
will catch it and display an error in the UI.

### Related issues
Should fix #492
This commit is contained in:
Ray 2025-12-20 14:39:48 -05:00 committed by GitHub
parent 5c5db4c1ef
commit 13d70e7623
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 32 deletions

View file

@ -252,14 +252,27 @@ class CollectionFolderViewModel
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
this@CollectionFolderViewModel.filter.value = filter this@CollectionFolderViewModel.filter.value = filter
} }
val newPager = createPager(sortAndDirection, recursive, filter, useSeriesForPrimary) try {
newPager.init() val newPager =
createPager(sortAndDirection, recursive, filter, useSeriesForPrimary).init()
if (newPager.isNotEmpty()) newPager.getBlocking(0) if (newPager.isNotEmpty()) newPager.getBlocking(0)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
pager.value = newPager pager.value = newPager
loading.value = LoadingState.Success loading.value = LoadingState.Success
backgroundLoading.value = LoadingState.Success backgroundLoading.value = LoadingState.Success
} }
} catch (ex: Exception) {
Timber.e(
ex,
"Exception while loading data: sort=%s, filter=%s",
sortAndDirection,
filter,
)
withContext(Dispatchers.Main) {
loading.value = LoadingState.Error(ex)
pager.value = listOf()
}
}
} }
} }

View file

@ -66,7 +66,7 @@ class ApiRequestPager<T>(
suspend fun init(initialPosition: Int = 0): ApiRequestPager<T> { suspend fun init(initialPosition: Int = 0): ApiRequestPager<T> {
if (totalCount < 0) { if (totalCount < 0) {
fetchPage(initialPosition, true).join() fetchPageBlocking(initialPosition, true)
} }
return this return this
} }
@ -75,7 +75,7 @@ class ApiRequestPager<T>(
if (index in 0..<totalCount) { if (index in 0..<totalCount) {
val item = items[index] val item = items[index]
if (item == null) { if (item == null) {
fetchPage(index, false) fetchPage(index)
} }
return item return item
} else { } else {
@ -87,7 +87,7 @@ class ApiRequestPager<T>(
if (index in 0..<totalCount) { if (index in 0..<totalCount) {
val item = items[index] val item = items[index]
if (item == null) { if (item == null) {
fetchPage(index, false).join() fetchPageBlocking(index, false)
return items[index] return items[index]
} }
return item return item
@ -110,14 +110,18 @@ class ApiRequestPager<T>(
override val size: Int override val size: Int
get() = totalCount get() = totalCount
private fun fetchPage( private fun fetchPage(position: Int): Job =
scope.launch(ExceptionHandler() + Dispatchers.IO) {
fetchPageBlocking(position, false)
}
private suspend fun fetchPageBlocking(
position: Int, position: Int,
setTotalCount: Boolean, setTotalCount: Boolean,
): Job = ) {
scope.launch(ExceptionHandler() + Dispatchers.IO) {
mutex.withLock { mutex.withLock {
val pageNumber = position / pageSize val pageNumber = position / pageSize
if (cachedPages.getIfPresent(pageNumber) == null) { if (cachedPages.getIfPresent(pageNumber) == null || setTotalCount) {
if (DEBUG) Timber.v("fetchPage: $pageNumber") if (DEBUG) Timber.v("fetchPage: $pageNumber")
val newRequest = val newRequest =
requestHandler.prepare( requestHandler.prepare(
@ -128,7 +132,7 @@ class ApiRequestPager<T>(
) )
val result = requestHandler.execute(api, newRequest).content val result = requestHandler.execute(api, newRequest).content
if (setTotalCount) { if (setTotalCount) {
totalCount = result.totalRecordCount totalCount = result.totalRecordCount.coerceAtLeast(0)
} }
val data = mutableListOf<BaseItem>() val data = mutableListOf<BaseItem>()
result.items.forEach { data.add(BaseItem.from(it, api, useSeriesForPrimary)) } result.items.forEach { data.add(BaseItem.from(it, api, useSeriesForPrimary)) }