Skip to content

Instantly share code, notes, and snippets.

@VahidGarousi
Created August 16, 2025 05:28
Show Gist options
  • Select an option

  • Save VahidGarousi/c6238d198eb511755fa78b87ed67321c to your computer and use it in GitHub Desktop.

Select an option

Save VahidGarousi/c6238d198eb511755fa78b87ed67321c to your computer and use it in GitHub Desktop.
Pagination
package ir.modjame.core.ui.paginator.manager
import ir.modjame.core.common.util.DataError
import ir.modjame.core.common.util.Result
import ir.modjame.core.common.util.map
import ir.modjame.core.domain.model.CursorPaginatedResult
import ir.modjame.core.domain.model.PaginatedResult
class CursorBasedPaginationManager<T>(
private var initialCursor: String?,
private val fetch: suspend (cursor: String) -> Result<CursorPaginatedResult<T>, DataError.Network>,
) : PaginationStrategy<T> {
private var currentCursor: String? = initialCursor
private var hasMore = true
override suspend fun fetchNext(): Result<PaginatedResult<T>, DataError.Network> {
if (!hasMore) return Result.Failure(DataError.Network.NOT_FOUND)
return currentCursor?.let { cursor ->
fetch(cursor).map { paginatedResult ->
currentCursor = paginatedResult.nextCursor
hasMore = paginatedResult.hasMore
PaginatedResult(
data = paginatedResult.posts,
totalPages = if (hasMore) Int.MAX_VALUE else 1,
)
}
} ?: Result.Failure(DataError.Network.UNKNOWN)
}
override fun reset() {
currentCursor = initialCursor
hasMore = true
}
override fun hasMore(): Boolean = hasMore
}
package ir.modjame.core.domain.model
data class CursorPaginatedResult<T>(
val posts: List<T>,
val nextCursor: String?,
val hasMore: Boolean
)
package ir.modjame.core.ui.paginator.manager
import ir.modjame.core.common.util.DataError
import ir.modjame.core.common.util.Result
import ir.modjame.core.common.util.onSuccess
import ir.modjame.core.domain.model.PaginatedResult
class PageBasedPaginationManager<T>(
private val initialPage: Int = 1,
private val fetch: suspend (page: Int) -> Result<PaginatedResult<T>, DataError.Network>,
) : PaginationStrategy<T> {
private var currentPage = initialPage
private var isLastPage = false
override suspend fun fetchNext(): Result<PaginatedResult<T>, DataError.Network> {
val result = fetch(currentPage)
result.onSuccess {
isLastPage = currentPage >= it.totalPages
currentPage++
}
return result
}
override fun reset() {
currentPage = initialPage
isLastPage = false
}
override fun hasMore() = !isLastPage
}
package ir.modjame.core.domain.model
data class PaginatedResult<T>(
val data: List<T>,
val totalPages: Int,
)
package ir.modjame.core.ui.paginator.state
import kotlinx.collections.immutable.ImmutableList
sealed class PaginatedState<out T> {
abstract val data: ImmutableList<T>?
data object NotLoaded : PaginatedState<Nothing>() {
override val data: Nothing? = null
}
data object InitialLoading : PaginatedState<Nothing>() {
override val data: Nothing? = null
}
data class LoadingMore<T>(
override val data: ImmutableList<T>,
) : PaginatedState<T>()
data class Loaded<T>(
override val data: ImmutableList<T>,
val isEndReached: Boolean,
) : PaginatedState<T>()
data class Error(
val message: String,
) : PaginatedState<Nothing>() {
override val data: ImmutableList<Nothing>? = null
}
}
fun <T> PaginatedState<T>.hasMore(): Boolean =
when (this) {
is PaginatedState.Loaded -> !isEndReached
is PaginatedState.NotLoaded,
is PaginatedState.InitialLoading,
is PaginatedState.LoadingMore,
is PaginatedState.Error,
-> true
}
package ir.modjame.core.ui.paginator.manager
import ir.modjame.core.common.util.fold
import ir.modjame.core.ui.paginator.state.PaginatedState
import kotlinx.collections.immutable.toPersistentList
class PaginationManager<T>(
private val strategy: PaginationStrategy<T>,
private val onStateUpdated: (PaginatedState<T>) -> Unit,
) {
private var isLoading = false
private val allItems = mutableListOf<T>()
suspend fun loadNextPage() {
if (isLoading || !strategy.hasMore()) return
isLoading = true
if (allItems.isEmpty()) {
onStateUpdated(PaginatedState.InitialLoading)
} else {
onStateUpdated(PaginatedState.LoadingMore(allItems.toPersistentList()))
}
strategy.fetchNext().fold(
onSuccess = { paginatedResult ->
allItems.addAll(paginatedResult.data)
onStateUpdated(
PaginatedState.Loaded(
data = allItems.toPersistentList(),
isEndReached = !strategy.hasMore(),
),
)
},
onFailure = { error ->
onStateUpdated(PaginatedState.Error(error.name))
},
)
isLoading = false
}
suspend fun refresh() {
strategy.reset()
allItems.clear()
loadNextPage()
}
}
package ir.modjame.core.ui.paginator.manager
import ir.modjame.core.common.util.DataError
import ir.modjame.core.common.util.Result
import ir.modjame.core.domain.model.PaginatedResult
interface PaginationStrategy<T> {
suspend fun fetchNext(): Result<PaginatedResult<T>, DataError.Network>
fun reset()
fun hasMore(): Boolean
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment