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,13 +252,26 @@ class CollectionFolderViewModel
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
this@CollectionFolderViewModel.filter.value = filter
}
val newPager = createPager(sortAndDirection, recursive, filter, useSeriesForPrimary)
newPager.init()
if (newPager.isNotEmpty()) newPager.getBlocking(0)
withContext(Dispatchers.Main) {
pager.value = newPager
loading.value = LoadingState.Success
backgroundLoading.value = LoadingState.Success
try {
val newPager =
createPager(sortAndDirection, recursive, filter, useSeriesForPrimary).init()
if (newPager.isNotEmpty()) newPager.getBlocking(0)
withContext(Dispatchers.Main) {
pager.value = newPager
loading.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> {
if (totalCount < 0) {
fetchPage(initialPosition, true).join()
fetchPageBlocking(initialPosition, true)
}
return this
}
@ -75,7 +75,7 @@ class ApiRequestPager<T>(
if (index in 0..<totalCount) {
val item = items[index]
if (item == null) {
fetchPage(index, false)
fetchPage(index)
}
return item
} else {
@ -87,7 +87,7 @@ class ApiRequestPager<T>(
if (index in 0..<totalCount) {
val item = items[index]
if (item == null) {
fetchPage(index, false).join()
fetchPageBlocking(index, false)
return items[index]
}
return item
@ -110,33 +110,37 @@ class ApiRequestPager<T>(
override val size: Int
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,
setTotalCount: Boolean,
): Job =
scope.launch(ExceptionHandler() + Dispatchers.IO) {
mutex.withLock {
val pageNumber = position / pageSize
if (cachedPages.getIfPresent(pageNumber) == null) {
if (DEBUG) Timber.v("fetchPage: $pageNumber")
val newRequest =
requestHandler.prepare(
request,
pageNumber * pageSize,
pageSize,
setTotalCount,
)
val result = requestHandler.execute(api, newRequest).content
if (setTotalCount) {
totalCount = result.totalRecordCount
}
val data = mutableListOf<BaseItem>()
result.items.forEach { data.add(BaseItem.from(it, api, useSeriesForPrimary)) }
cachedPages.put(pageNumber, data)
items = ItemList(totalCount, pageSize, cachedPages.asMap())
) {
mutex.withLock {
val pageNumber = position / pageSize
if (cachedPages.getIfPresent(pageNumber) == null || setTotalCount) {
if (DEBUG) Timber.v("fetchPage: $pageNumber")
val newRequest =
requestHandler.prepare(
request,
pageNumber * pageSize,
pageSize,
setTotalCount,
)
val result = requestHandler.execute(api, newRequest).content
if (setTotalCount) {
totalCount = result.totalRecordCount.coerceAtLeast(0)
}
val data = mutableListOf<BaseItem>()
result.items.forEach { data.add(BaseItem.from(it, api, useSeriesForPrimary)) }
cachedPages.put(pageNumber, data)
items = ItemList(totalCount, pageSize, cachedPages.asMap())
}
}
}
suspend fun refreshItem(
position: Int,