fix of InfinityPagedComponent

This commit is contained in:
2025-03-07 18:05:20 +06:00
parent a9859f6a0d
commit eda6221288
2 changed files with 41 additions and 23 deletions

View File

@@ -2,7 +2,13 @@ package dev.inmo.micro_utils.pagination.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.runCatchingLogging
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/** /**
* Context for managing infinite pagination in a Compose UI. * Context for managing infinite pagination in a Compose UI.
@@ -16,30 +22,46 @@ import dev.inmo.micro_utils.pagination.*
*/ */
class InfinityPagedComponentContext<T> internal constructor( class InfinityPagedComponentContext<T> internal constructor(
page: Int, page: Int,
size: Int size: Int,
private val scope: CoroutineScope,
private val loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>
) { ) {
internal val startPage = SimplePagination(page, size) internal val startPage = SimplePagination(page, size)
internal val currentlyLoadingPage = SpecialMutableStateFlow<Pagination?>(startPage)
internal val latestLoadedPage = SpecialMutableStateFlow<PaginationResult<T>?>(null) internal val latestLoadedPage = SpecialMutableStateFlow<PaginationResult<T>?>(null)
internal val dataState = SpecialMutableStateFlow<List<T>?>(null) internal val dataState = SpecialMutableStateFlow<List<T>?>(null)
internal var loadingJob: Job? = null
internal val loadingMutex = Mutex()
/** /**
* Loads the next page of data. If the current page is the last one, the function returns early. * Loads the next page of data. If the current page is the last one, the function returns early.
*/ */
fun loadNext() { fun loadNext(): Job {
if (latestLoadedPage.value ?.isLastPage == true) return return scope.launchLoggingDropExceptions {
if (currentlyLoadingPage.value != null) return // Data loading has been inited but not loaded yet loadingMutex.withLock {
if (latestLoadedPage.value ?.isLastPage == true) return@launchLoggingDropExceptions
currentlyLoadingPage.value = latestLoadedPage.value ?.nextPage() ?: startPage loadingJob = loadingJob ?: scope.launchLoggingDropExceptions {
runCatching {
loader(latestLoadedPage.value ?.nextPage() ?: startPage)
}.onSuccess {
latestLoadedPage.value = it
dataState.value = (dataState.value ?: emptyList()) + it.results
}
loadingMutex.withLock {
loadingJob = null
}
}
loadingJob
} ?.join()
}
} }
/** /**
* Reloads the pagination from the first page, clearing previously loaded data. * Reloads the pagination from the first page, clearing previously loaded data.
*/ */
fun reload() { fun reload(): Job {
latestLoadedPage.value = null latestLoadedPage.value = null
currentlyLoadingPage.value = null dataState.value = null
loadNext() return loadNext()
} }
} }
@@ -58,18 +80,11 @@ internal fun <T> InfinityPagedComponent(
page: Int, page: Int,
size: Int, size: Int,
loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>, loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
predefinedScope: CoroutineScope? = null,
block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit
) { ) {
val context = remember { InfinityPagedComponentContext<T>(page, size) } val scope = predefinedScope ?: rememberCoroutineScope()
val context = remember { InfinityPagedComponentContext<T>(page, size, scope, loader) }
val currentlyLoadingState = context.currentlyLoadingPage.collectAsState()
LaunchedEffect(currentlyLoadingState.value) {
val paginationResult = loader(context, currentlyLoadingState.value ?: return@LaunchedEffect)
context.latestLoadedPage.value = paginationResult
context.currentlyLoadingPage.value = null
context.dataState.value = (context.dataState.value ?: emptyList()) + paginationResult.results
}
val dataState = context.dataState.collectAsState() val dataState = context.dataState.collectAsState()
context.block(dataState.value) context.block(dataState.value)
@@ -88,12 +103,14 @@ internal fun <T> InfinityPagedComponent(
fun <T> InfinityPagedComponent( fun <T> InfinityPagedComponent(
pageInfo: Pagination, pageInfo: Pagination,
loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>, loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
predefinedScope: CoroutineScope? = null,
block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit
) { ) {
InfinityPagedComponent( InfinityPagedComponent(
pageInfo.page, pageInfo.page,
pageInfo.size, pageInfo.size,
loader, loader,
predefinedScope,
block block
) )
} }
@@ -111,7 +128,8 @@ fun <T> InfinityPagedComponent(
fun <T> InfinityPagedComponent( fun <T> InfinityPagedComponent(
size: Int, size: Int,
loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>, loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
predefinedScope: CoroutineScope? = null,
block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit
) { ) {
InfinityPagedComponent(0, size, loader, block) InfinityPagedComponent(0, size, loader, predefinedScope, block)
} }

View File

@@ -30,13 +30,13 @@ class InfinityPagedComponentTests {
} }
) { ) {
if (it == null) { if (it == null) {
assertEquals(0, this.currentlyLoadingPage.value ?.page) assertEquals(null, it)
} else { } else {
assertEquals(expectedList, it) assertEquals(expectedList, it)
} }
LaunchedEffect(it ?.size) { LaunchedEffect(it ?.size) {
loadNext() loadNext().join()
} }
} }
} }