diff --git a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt index 377fe15a6c2..f11d563ae54 100644 --- a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt +++ b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt @@ -2,7 +2,13 @@ package dev.inmo.micro_utils.pagination.compose import androidx.compose.runtime.* 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 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. @@ -16,30 +22,46 @@ import dev.inmo.micro_utils.pagination.* */ class InfinityPagedComponentContext internal constructor( page: Int, - size: Int + size: Int, + private val scope: CoroutineScope, + private val loader: suspend InfinityPagedComponentContext.(Pagination) -> PaginationResult ) { internal val startPage = SimplePagination(page, size) - internal val currentlyLoadingPage = SpecialMutableStateFlow(startPage) internal val latestLoadedPage = SpecialMutableStateFlow?>(null) internal val dataState = SpecialMutableStateFlow?>(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. */ - fun loadNext() { - if (latestLoadedPage.value ?.isLastPage == true) return - if (currentlyLoadingPage.value != null) return // Data loading has been inited but not loaded yet - - currentlyLoadingPage.value = latestLoadedPage.value ?.nextPage() ?: startPage + fun loadNext(): Job { + return scope.launchLoggingDropExceptions { + loadingMutex.withLock { + if (latestLoadedPage.value ?.isLastPage == true) return@launchLoggingDropExceptions + 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. */ - fun reload() { + fun reload(): Job { latestLoadedPage.value = null - currentlyLoadingPage.value = null - loadNext() + dataState.value = null + return loadNext() } } @@ -58,18 +80,11 @@ internal fun InfinityPagedComponent( page: Int, size: Int, loader: suspend InfinityPagedComponentContext.(Pagination) -> PaginationResult, + predefinedScope: CoroutineScope? = null, block: @Composable InfinityPagedComponentContext.(List?) -> Unit ) { - val context = remember { InfinityPagedComponentContext(page, size) } - - 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 scope = predefinedScope ?: rememberCoroutineScope() + val context = remember { InfinityPagedComponentContext(page, size, scope, loader) } val dataState = context.dataState.collectAsState() context.block(dataState.value) @@ -88,12 +103,14 @@ internal fun InfinityPagedComponent( fun InfinityPagedComponent( pageInfo: Pagination, loader: suspend InfinityPagedComponentContext.(Pagination) -> PaginationResult, + predefinedScope: CoroutineScope? = null, block: @Composable InfinityPagedComponentContext.(List?) -> Unit ) { InfinityPagedComponent( pageInfo.page, pageInfo.size, loader, + predefinedScope, block ) } @@ -111,7 +128,8 @@ fun InfinityPagedComponent( fun InfinityPagedComponent( size: Int, loader: suspend InfinityPagedComponentContext.(Pagination) -> PaginationResult, + predefinedScope: CoroutineScope? = null, block: @Composable InfinityPagedComponentContext.(List?) -> Unit ) { - InfinityPagedComponent(0, size, loader, block) + InfinityPagedComponent(0, size, loader, predefinedScope, block) } diff --git a/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt b/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt index daabfeca6ac..fe1f84f349c 100644 --- a/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt +++ b/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt @@ -30,13 +30,13 @@ class InfinityPagedComponentTests { } ) { if (it == null) { - assertEquals(0, this.currentlyLoadingPage.value ?.page) + assertEquals(null, it) } else { assertEquals(expectedList, it) } LaunchedEffect(it ?.size) { - loadNext() + loadNext().join() } } }