diff --git a/core/pagination/src/main/java/com/tangem/pagination/BatchAction.kt b/core/pagination/src/main/java/com/tangem/pagination/BatchAction.kt index 4a4cfd7d71..813cbe6ba9 100644 --- a/core/pagination/src/main/java/com/tangem/pagination/BatchAction.kt +++ b/core/pagination/src/main/java/com/tangem/pagination/BatchAction.kt @@ -14,9 +14,9 @@ sealed class BatchAction { * * @param request request to load the first batch. */ - data class Reload( - val request: R, - ) : BatchAction() + data class Reload( + val request: TRequest, + ) : BatchAction() /** * Action to load the next batch. @@ -25,9 +25,9 @@ sealed class BatchAction { * If null, the last request will be used. * Will be saved in the state and used for future LoadMore actions with request = null. */ - data class LoadMore( - val request: R? = null, - ) : BatchAction() + data class LoadMore( + val request: TRequest? = null, + ) : BatchAction() /** * Action to update the batch. @@ -35,10 +35,10 @@ sealed class BatchAction { * @param keys keys of the batches to update. * @param request request to update the batches. */ - class UpdateBatches( - val keys: Set, - val request: U, - ) : BatchAction() + class UpdateBatches( + val keys: Set, + val request: TUpdate, + ) : BatchAction() /** * Action to cancel the current batch loading. @@ -55,7 +55,7 @@ sealed class BatchAction { * * @param predicate predicate to check if the update request should be cancelled. */ - class CancelUpdates( - val predicate: (UpdateBatches) -> Boolean, - ) : BatchAction() + class CancelUpdates( + val predicate: (UpdateBatches) -> Boolean, + ) : BatchAction() } \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/BatchFetchResult.kt b/core/pagination/src/main/java/com/tangem/pagination/BatchFetchResult.kt index 03f87be764..c3b8b03ee2 100644 --- a/core/pagination/src/main/java/com/tangem/pagination/BatchFetchResult.kt +++ b/core/pagination/src/main/java/com/tangem/pagination/BatchFetchResult.kt @@ -29,9 +29,10 @@ sealed class BatchFetchResult { /** * Represents an unknown error result of a batch fetch request. - * Used for unexpected exceptions that occurred in fetch method in [BatchFetcher]. + * Used for unexpected exceptions that occurred in fetch method in BatchFetcher. * * @param throwable throwable that occurred during the request. + * @see com.tangem.pagination.fetcher.BatchFetcher */ class UnknownError(val throwable: Throwable) : BatchFetchResult() } \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/BatchFetcher.kt b/core/pagination/src/main/java/com/tangem/pagination/BatchFetcher.kt deleted file mode 100644 index aea9c1bd37..0000000000 --- a/core/pagination/src/main/java/com/tangem/pagination/BatchFetcher.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.pagination - -/** - * Interface for fetching a batch of data. Used in [BatchListState]. - * - * @param TRequest type of the request. - * @param TData type of the data. - * @param TError type of the error. - * - * @see BatchListState - */ -interface BatchFetcher { - - /** - * Fetches a batch of data. - * - * @param request request to fetch the data. - * @return result of the fetch operation. - */ - suspend fun fetch(request: BatchRequest): BatchFetchResult -} \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt b/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt index a3e529d029..7021d474c3 100644 --- a/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt +++ b/core/pagination/src/main/java/com/tangem/pagination/BatchListSource.kt @@ -1,5 +1,6 @@ package com.tangem.pagination +import com.tangem.pagination.fetcher.BatchFetcher import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* @@ -30,13 +31,11 @@ interface BatchListSource { * @return New instance of [BatchListSource]. */ @Suppress("FunctionNaming") -fun BatchListSource( - config: BatchingConfig, +fun BatchListSource( context: BatchingContext, generateNewKey: suspend (List) -> TKey, batchFetcher: BatchFetcher, -): BatchListSource = - BatchListSourceImpl(config, context, generateNewKey, batchFetcher, null) +): BatchListSource = BatchListSourceImpl(context, generateNewKey, batchFetcher, null) /** * Creates a new [BatchListSource] with the provided configuration. @@ -50,17 +49,15 @@ fun BatchListSource( * @return New instance of [BatchListSource]. */ @Suppress("FunctionNaming") -fun BatchListSource( - config: BatchingConfig, +fun BatchListSource( context: BatchingContext, generateNewKey: suspend (List) -> TKey, batchFetcher: BatchFetcher, updateFetcher: BatchUpdateFetcher, ): BatchListSource = - BatchListSourceImpl(config, context, generateNewKey, batchFetcher, updateFetcher) + BatchListSourceImpl(context, generateNewKey, batchFetcher, updateFetcher) -private class BatchListSourceImpl( - private val config: BatchingConfig, +private class BatchListSourceImpl( private val context: BatchingContext, private val generateNewKey: suspend (List) -> TKey, private val batchFetcher: BatchFetcher, @@ -78,7 +75,7 @@ private class BatchListSourceImpl( private val waitingUpdateJobs = MutableStateFlow, Job>>>(emptyList()) - private val lastRequest = MutableStateFlow?>(null) + private val lastRequestResult = MutableStateFlow?>(null) private var reloadActionJob: Job? = null private var loadMoreActionJob: Job? = null @@ -88,10 +85,9 @@ private class BatchListSourceImpl( awaitCancellation() } finally { withContext(NonCancellable) { - lastRequest.value = null loadMoreActionJob = null loadMoreActionJob = null - lastRequest.value = null + lastRequestResult.value = null stopAllUpdates() state.value = BatchListState(emptyList(), PaginationStatus.None) } @@ -178,11 +174,8 @@ private class BatchListSourceImpl( status = PaginationStatus.InitialLoading, ) - val requestData = action.request - val request = BatchRequest(offset = 0, limit = config.batchSize, requestData) - val res = runCatching { - batchFetcher.fetch(request) + batchFetcher.fetchFirst(action.request) }.getOrElse { BatchFetchResult.UnknownError(it) } state.value = if (res is BatchFetchResult.Success) { @@ -208,52 +201,24 @@ private class BatchListSourceImpl( ) } - lastRequest.value = request + lastRequestResult.value = res } private suspend fun loadMoreTask(action: BatchAction.LoadMore) { val status = state.value.status - val lastReq = lastRequest.value - val request: BatchRequest = when { - // try to continue pagination with new request - status is PaginationStatus.EndOfPagination && action.request != null && action.request != lastReq -> { - requireNotNull(lastReq) + if (status !is PaginationStatus.Paginating && status !is PaginationStatus.EndOfPagination) return + if (status is PaginationStatus.EndOfPagination && action.request == null) return - BatchRequest( - offset = lastReq.offset + lastReq.limit, - limit = config.batchSize, - data = action.request, - ) - } - // continue pagination - status is PaginationStatus.Paginating -> { - requireNotNull(lastReq) - - if (status.lastResult is BatchFetchResult.Success) { - BatchRequest( - offset = lastReq.offset + lastReq.limit, - limit = config.batchSize, - data = action.request ?: lastReq.data, - ) - } else { - BatchRequest( - offset = lastReq.offset, - limit = lastReq.limit, - data = action.request ?: lastReq.data, - ) - } - } - else -> return - } + val lastResult = lastRequestResult.value ?: return state.update { it.copy(status = PaginationStatus.NextBatchLoading) } val res = runCatching { - batchFetcher.fetch(request) + batchFetcher.fetchNext(action.request, lastResult) }.getOrElse { BatchFetchResult.UnknownError(it) } - lastRequest.value = request + lastRequestResult.value = lastResult state.update { currentState -> if (res is BatchFetchResult.Success) { diff --git a/core/pagination/src/main/java/com/tangem/pagination/BatchRequest.kt b/core/pagination/src/main/java/com/tangem/pagination/BatchRequest.kt deleted file mode 100644 index b6c9545527..0000000000 --- a/core/pagination/src/main/java/com/tangem/pagination/BatchRequest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.pagination - -/** - * Request to fetch a batch of data. - * - * @param TRequest type of the request. - * - * @property offset offset for the request. - * @property limit limit for the request. - * @property data body of the request. - * - * @see BatchFetcher - */ -data class BatchRequest( - val offset: Int, - val limit: Int, - val data: TRequest, -) \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/exception/EndOfPaginationException.kt b/core/pagination/src/main/java/com/tangem/pagination/exception/EndOfPaginationException.kt new file mode 100644 index 0000000000..f2cc69767a --- /dev/null +++ b/core/pagination/src/main/java/com/tangem/pagination/exception/EndOfPaginationException.kt @@ -0,0 +1,6 @@ +package com.tangem.pagination.exception + +/** + * Exception that is thrown when there are no more items to fetch. + */ +class EndOfPaginationException : IllegalStateException() \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/fetcher/BatchFetcher.kt b/core/pagination/src/main/java/com/tangem/pagination/fetcher/BatchFetcher.kt new file mode 100644 index 0000000000..901d83f32b --- /dev/null +++ b/core/pagination/src/main/java/com/tangem/pagination/fetcher/BatchFetcher.kt @@ -0,0 +1,37 @@ +package com.tangem.pagination.fetcher + +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.BatchListState + +/** + * Interface for fetching a batch of data. Used in [BatchListState]. + * + * @param TRequest type of the request. + * @param TData type of the data. + * @param TError type of the error. + * + * @see BatchListState + */ +interface BatchFetcher { + + /** + * Fetches the first batch of data. + * + * @param request initial request. Will be saved to be used in [fetchNext] requests. + * @return result of the fetch operation. + */ + suspend fun fetchFirst(request: TRequest): BatchFetchResult + + /** + * Fetches the next batch of data. + * + * @param overrideRequest overrides current remembered request, even if that fetch fails. + * If null, the last request should be used. + * @param lastResult result of the last fetch operation. + * @return result of the fetch operation. + */ + suspend fun fetchNext( + overrideRequest: TRequest?, + lastResult: BatchFetchResult, + ): BatchFetchResult +} \ No newline at end of file diff --git a/core/pagination/src/main/java/com/tangem/pagination/fetcher/LimitOffsetBatchFetcher.kt b/core/pagination/src/main/java/com/tangem/pagination/fetcher/LimitOffsetBatchFetcher.kt new file mode 100644 index 0000000000..11af45665d --- /dev/null +++ b/core/pagination/src/main/java/com/tangem/pagination/fetcher/LimitOffsetBatchFetcher.kt @@ -0,0 +1,69 @@ +package com.tangem.pagination.fetcher + +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.exception.EndOfPaginationException +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * Fetcher that uses limit and offset to fetch data. + * + * @param TRequest type of the request. + * @param TData type of the data. + * @param TError type of the error. + * + * @property prefetchDistance number of items to fetch for the first batch. + * @property batchSize size of the batch. + * @property fetch function that fetches the data. + */ +class LimitOffsetBatchFetcher( + private val prefetchDistance: Int, + private val batchSize: Int, + private val fetch: (request: Request) -> BatchFetchResult, +) : BatchFetcher { + + data class Request( + val limit: Int, + val offset: Int, + val request: TRequest, + ) + + private val lastRequest = MutableStateFlow?>(null) + + override suspend fun fetchFirst(request: TRequest): BatchFetchResult { + val req = Request( + offset = 0, + limit = prefetchDistance, + request = request, + ) + + val res = fetch(req) + lastRequest.value = req + return res + } + + override suspend fun fetchNext( + overrideRequest: TRequest?, + lastResult: BatchFetchResult, + ): BatchFetchResult { + val last = lastRequest.value + requireNotNull(last) + + val req = if (lastResult is BatchFetchResult.Success) { + if (lastResult.last && overrideRequest == null) { + return BatchFetchResult.UnknownError(EndOfPaginationException()) + } + + Request( + offset = last.offset + last.limit, + limit = batchSize, + request = overrideRequest ?: last.request, + ) + } else { + last + } + + val res = fetch(req) + lastRequest.value = req + return res + } +} \ No newline at end of file