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.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 =
if (newPager.isNotEmpty()) newPager.getBlocking(0) createPager(sortAndDirection, recursive, filter, useSeriesForPrimary).init()
withContext(Dispatchers.Main) { if (newPager.isNotEmpty()) newPager.getBlocking(0)
pager.value = newPager withContext(Dispatchers.Main) {
loading.value = LoadingState.Success pager.value = newPager
backgroundLoading.value = LoadingState.Success 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> { 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,33 +110,37 @@ 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 || setTotalCount) {
if (cachedPages.getIfPresent(pageNumber) == null) { if (DEBUG) Timber.v("fetchPage: $pageNumber")
if (DEBUG) Timber.v("fetchPage: $pageNumber") val newRequest =
val newRequest = requestHandler.prepare(
requestHandler.prepare( request,
request, pageNumber * pageSize,
pageNumber * pageSize, pageSize,
pageSize, setTotalCount,
setTotalCount, )
) val result = requestHandler.execute(api, newRequest).content
val result = requestHandler.execute(api, newRequest).content if (setTotalCount) {
if (setTotalCount) { totalCount = result.totalRecordCount.coerceAtLeast(0)
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())
} }
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( suspend fun refreshItem(
position: Int, position: Int,