Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-03 19:27:25 +03:00
parent dc97dbd91f
commit dd43f3dbf5
7 changed files with 42 additions and 38 deletions

View file

@ -17,13 +17,13 @@ import kotlinx.coroutines.flow.*
*/
interface BatchListSource<TKey, TData, TUpdate, TError> {
val state: StateFlow<BatchListState<TKey, TData, TError>>
val updateResults: SharedFlow<Pair<TUpdate, BatchFetchUpdateResult<TKey, TData, TError>>>
val updateResults: SharedFlow<Pair<TUpdate, FetchUpdateResult<TKey, TData, TError>>>
}
/**
* Creates a new [BatchListSource] with the provided configuration.
*
* @param config Configuration for batching.
* @param ioDispatcher Dispatcher for IO operations.
* @param context Context for batching.
* @param generateNewKey Function to generate a new key for a batch.
* @param batchFetcher Function to fetch a batch of data.
@ -32,15 +32,17 @@ interface BatchListSource<TKey, TData, TUpdate, TError> {
*/
@Suppress("FunctionNaming")
fun <TKey, TData, TRequest : Any, TError> BatchListSource(
ioDispatcher : CoroutineDispatcher = Dispatchers.IO,
context: BatchingContext<TRequest, TKey, Nothing>,
generateNewKey: suspend (List<TKey>) -> TKey,
batchFetcher: BatchFetcher<TRequest, TData, TError>,
): BatchListSource<TKey, TData, Nothing, TError> = BatchListSourceImpl(context, generateNewKey, batchFetcher, null)
): BatchListSource<TKey, TData, Nothing, TError> =
BatchListSourceImpl(ioDispatcher, context, generateNewKey, batchFetcher, null)
/**
* Creates a new [BatchListSource] with the provided configuration.
*
* @param config Configuration for batching.
* @param ioDispatcher Dispatcher for IO operations.
* @param context Context for batching.
* @param generateNewKey Function to generate a new key for a batch.
* @param batchFetcher Function to fetch a batch of data.
@ -50,14 +52,16 @@ fun <TKey, TData, TRequest : Any, TError> BatchListSource(
*/
@Suppress("FunctionNaming")
fun <TKey, TData, TUpdate, TRequest : Any, TError> BatchListSource(
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
context: BatchingContext<TRequest, TKey, TUpdate>,
generateNewKey: suspend (List<TKey>) -> TKey,
batchFetcher: BatchFetcher<TRequest, TData, TError>,
updateFetcher: BatchUpdateFetcher<TKey, TData, TError, TUpdate>,
): BatchListSource<TKey, TData, TUpdate, TError> =
BatchListSourceImpl(context, generateNewKey, batchFetcher, updateFetcher)
BatchListSourceImpl(ioDispatcher, context, generateNewKey, batchFetcher, updateFetcher)
private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
private val ioDispatcher: CoroutineDispatcher,
private val context: BatchingContext<TRequest, TKey, TUpdate>,
private val generateNewKey: suspend (List<TKey>) -> TKey,
private val batchFetcher: BatchFetcher<TRequest, TData, TError>,
@ -65,7 +69,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
) : BatchListSource<TKey, TData, TUpdate, TError> {
override val state = MutableStateFlow(BatchListState<TKey, TData, TError>(emptyList(), PaginationStatus.None))
override val updateResults = MutableSharedFlow<Pair<TUpdate, BatchFetchUpdateResult<TKey, TData, TError>>>(
override val updateResults = MutableSharedFlow<Pair<TUpdate, FetchUpdateResult<TKey, TData, TError>>>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
@ -75,7 +79,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
private val waitingUpdateJobs =
MutableStateFlow<List<Pair<BatchAction.UpdateBatches<TKey, TUpdate>, Job>>>(emptyList())
private val lastRequestResult = MutableStateFlow<BatchFetchResult<TData, TError>?>(null)
private val lastRequestResult = MutableStateFlow<FetchResult<TData, TError>?>(null)
private var reloadActionJob: Job? = null
private var loadMoreActionJob: Job? = null
@ -108,7 +112,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
loadMoreActionJob?.cancel()
reloadActionJob?.cancel()
stopAllUpdates()
reloadActionJob = scope.launch(Dispatchers.IO) {
reloadActionJob = scope.launch(ioDispatcher) {
reloadTask(action)
}
}
@ -117,7 +121,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
return
}
loadMoreActionJob = scope.launch(Dispatchers.IO) {
loadMoreActionJob = scope.launch(ioDispatcher) {
reloadActionJob?.join()
loadMoreTask(action)
}
@ -125,7 +129,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
is BatchAction.UpdateBatches -> {
if (updateFetcher == null) return
scope.launch(Dispatchers.IO) {
scope.launch(ioDispatcher) {
val job = launch(start = CoroutineStart.LAZY) {
updateBatchesTask(action)
}
@ -176,9 +180,9 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
val res = runCatching {
batchFetcher.fetchFirst(action.request)
}.getOrElse { BatchFetchResult.UnknownError(it) }
}.getOrElse { FetchResult.UnknownError(it) }
state.value = if (res is BatchFetchResult.Success) {
state.value = if (res is FetchResult.Success) {
val key = generateNewKey(listOf())
val batch = Batch(
key = key,
@ -196,7 +200,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
BatchListState(
data = emptyList(),
status = PaginationStatus.InitialLoadingError(
error = (res as? BatchFetchResult.Error)?.error,
error = (res as? FetchResult.Error)?.error,
),
)
}
@ -216,12 +220,12 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
val res = runCatching {
batchFetcher.fetchNext(action.request, lastResult)
}.getOrElse { BatchFetchResult.UnknownError(it) }
}.getOrElse { FetchResult.UnknownError(it) }
lastRequestResult.value = lastResult
state.update { currentState ->
if (res is BatchFetchResult.Success) {
if (res is FetchResult.Success) {
val newBatch = Batch(
key = generateNewKey(currentState.data.map { it.key }),
data = res.data,
@ -254,7 +258,7 @@ private class BatchListSourceImpl<TKey, TData, TUpdate, TRequest : Any, TError>(
updateRequest = action.request,
)
if (result is BatchFetchUpdateResult.Success) {
if (result is FetchUpdateResult.Success) {
state.update { currentState ->
val resMap = result.data.associateBy { it.key }
currentState.copy(

View file

@ -21,5 +21,5 @@ interface BatchUpdateFetcher<TKey, TData, TError, TUpdate> {
suspend fun fetchUpdate(
toUpdate: List<Batch<TKey, TData>>,
updateRequest: TUpdate,
): BatchFetchUpdateResult<TKey, TData, TError>
): FetchUpdateResult<TKey, TData, TError>
}

View file

@ -7,7 +7,7 @@ package com.tangem.pagination
* @param TData type of the data.
* @param TError type of the error.
*/
sealed class BatchFetchResult<out TData, out TError> {
sealed class FetchResult<out TData, out TError> {
/**
* Represents a successful result of a batch fetch request.
@ -18,14 +18,14 @@ sealed class BatchFetchResult<out TData, out TError> {
data class Success<TData>(
val data: TData,
val last: Boolean = false,
) : BatchFetchResult<TData, Nothing>()
) : FetchResult<TData, Nothing>()
/**
* Represents an error result of a batch fetch request.
*
* @param error error that occurred during the request.
*/
data class Error<TError>(val error: TError) : BatchFetchResult<Nothing, TError>()
data class Error<TError>(val error: TError) : FetchResult<Nothing, TError>()
/**
* Represents an unknown error result of a batch fetch request.
@ -34,5 +34,5 @@ sealed class BatchFetchResult<out TData, out TError> {
* @param throwable throwable that occurred during the request.
* @see com.tangem.pagination.fetcher.BatchFetcher
*/
class UnknownError(val throwable: Throwable) : BatchFetchResult<Nothing, Nothing>()
class UnknownError(val throwable: Throwable) : FetchResult<Nothing, Nothing>()
}

View file

@ -8,7 +8,7 @@ package com.tangem.pagination
* @param TData type of the data.
* @param TError type of the error.
*/
sealed class BatchFetchUpdateResult<out TKey, out TData, out TError> {
sealed class FetchUpdateResult<out TKey, out TData, out TError> {
/**
* Represents a successful result of a batch update operation.
@ -17,14 +17,14 @@ sealed class BatchFetchUpdateResult<out TKey, out TData, out TError> {
*/
data class Success<TKey, TData>(
val data: List<Batch<TKey, TData>>,
) : BatchFetchUpdateResult<TKey, TData, Nothing>()
) : FetchUpdateResult<TKey, TData, Nothing>()
/**
* Represents an error result of a batch update operation.
*
* @param error error that occurred during the operation.
*/
data class Error<TError>(val error: TError) : BatchFetchUpdateResult<Nothing, Nothing, TError>()
data class Error<TError>(val error: TError) : FetchUpdateResult<Nothing, Nothing, TError>()
/**
* Represents an unknown error result of a batch update operation.
@ -32,5 +32,5 @@ sealed class BatchFetchUpdateResult<out TKey, out TData, out TError> {
*
* @param throwable throwable that occurred during the operation.
*/
class UnknownError(val throwable: Throwable) : BatchFetchUpdateResult<Nothing, Nothing, Nothing>()
class UnknownError(val throwable: Throwable) : FetchUpdateResult<Nothing, Nothing, Nothing>()
}

View file

@ -34,12 +34,12 @@ sealed class PaginationStatus<out T, out E> {
/**
* Represents that the last batch was loaded and
* the source is ready to load the next one or reload previous if [lastResult] is an error.
* For the first batch, [lastResult] is always [BatchFetchResult.Success]
* For the first batch, [lastResult] is always [FetchResult.Success]
*
* @param lastResult result of the last batch fetch.
*/
data class Paginating<out T, out E>(
val lastResult: BatchFetchResult<T, E>,
val lastResult: FetchResult<T, E>,
) : PaginationStatus<T, E>()
/**

View file

@ -1,6 +1,6 @@
package com.tangem.pagination.fetcher
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.FetchResult
import com.tangem.pagination.BatchListState
/**
@ -20,7 +20,7 @@ interface BatchFetcher<TRequest : Any, TData, TError> {
* @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<TData, TError>
suspend fun fetchFirst(request: TRequest): FetchResult<TData, TError>
/**
* Fetches the next batch of data.
@ -32,6 +32,6 @@ interface BatchFetcher<TRequest : Any, TData, TError> {
*/
suspend fun fetchNext(
overrideRequest: TRequest?,
lastResult: BatchFetchResult<TData, TError>,
): BatchFetchResult<TData, TError>
lastResult: FetchResult<TData, TError>,
): FetchResult<TData, TError>
}

View file

@ -1,6 +1,6 @@
package com.tangem.pagination.fetcher
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.FetchResult
import com.tangem.pagination.exception.EndOfPaginationException
import kotlinx.coroutines.flow.MutableStateFlow
@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
class LimitOffsetBatchFetcher<TRequest : Any, TData, TError>(
private val prefetchDistance: Int,
private val batchSize: Int,
private val fetch: (request: Request<TRequest>) -> BatchFetchResult<TData, TError>,
private val fetch: (request: Request<TRequest>) -> FetchResult<TData, TError>,
) : BatchFetcher<TRequest, TData, TError> {
data class Request<TRequest>(
@ -29,7 +29,7 @@ class LimitOffsetBatchFetcher<TRequest : Any, TData, TError>(
private val lastRequest = MutableStateFlow<Request<TRequest>?>(null)
override suspend fun fetchFirst(request: TRequest): BatchFetchResult<TData, TError> {
override suspend fun fetchFirst(request: TRequest): FetchResult<TData, TError> {
val req = Request(
offset = 0,
limit = prefetchDistance,
@ -43,14 +43,14 @@ class LimitOffsetBatchFetcher<TRequest : Any, TData, TError>(
override suspend fun fetchNext(
overrideRequest: TRequest?,
lastResult: BatchFetchResult<TData, TError>,
): BatchFetchResult<TData, TError> {
lastResult: FetchResult<TData, TError>,
): FetchResult<TData, TError> {
val last = lastRequest.value
requireNotNull(last)
val req = if (lastResult is BatchFetchResult.Success) {
val req = if (lastResult is FetchResult.Success) {
if (lastResult.last && overrideRequest == null) {
return BatchFetchResult.UnknownError(EndOfPaginationException())
return FetchResult.UnknownError(EndOfPaginationException())
}
Request(