KNET oferuje helper do obsługi paginacji: offset, cursor i Link header.
import rip.nerd.kitsunenet.pagination.KNETPagination
val pagination = KNETPagination.offset(
pageSize = 20,
startPage = 1
)
// Pierwsza strona
val page1 = client.get("$baseUrl/users", query = pagination.params)
// ?page=1&limit=20
// Następna strona
pagination.nextPage()
val page2 = client.get("$baseUrl/users", query = pagination.params)
// ?page=2&limit=20
// Iteruj wszystkie strony
pagination.forEachPage { params ->
val response = client.get("$baseUrl/users", query = params)
val users = response.jsonList<User>()
if (users.isEmpty()) {
return@forEachPage false // Stop
}
processUsers(users)
true // Continue
}
val pagination = KNETPagination.cursor(
limitParam = "limit",
cursorParam = "cursor",
pageSize = 20
)
// Pierwsza strona
val response1 = client.get("$baseUrl/users", query = pagination.params)
// ?limit=20
// Pobierz cursor z response
val nextCursor = response1.jsonPath("meta.next_cursor") as? String
pagination.setCursor(nextCursor)
// Następna strona
val response2 = client.get("$baseUrl/users", query = pagination.params)
// ?limit=20&cursor=abc123
// Auto-paginacja
pagination.fetchAll(client, "$baseUrl/users") { response ->
val data = response.jsonObject()
val cursor = data["meta"]?.let { (it as Map<*,*>)["next_cursor"] } as? String
val items = (data["data"] as List<*>).map { User.fromMap(it as Map<*,*>) }
PaginationResult(items, cursor)
}
// Automatyczne parsowanie Link header (RFC 5988)
val pagination = KNETPagination.linkHeader()
val response = client.get("$baseUrl/users?page=1")
// Link: </users?page=2>; rel="next", </users?page=10>; rel="last"
val links = pagination.parseLinks(response)
println(links.next) // "/users?page=2"
println(links.last) // "/users?page=10"
println(links.hasNext) // true
// Fetch next
if (links.hasNext) {
val nextResponse = client.get("$baseUrl${links.next}")
}
fun getUsers(): Flow<User> = flow {
val pagination = KNETPagination.offset(pageSize = 20)
while (true) {
val response = client.get("$baseUrl/users", query = pagination.params)
val users = response.jsonList<User>()
if (users.isEmpty()) break
users.forEach { emit(it) }
pagination.nextPage()
}
}
// Użycie
getUsers()
.take(100) // Max 100 users
.collect { user ->
displayUser(user)
}
class UserPagingSource(
private val client: KNETClient
) : PagingSource<Int, User>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, User> {
val page = params.key ?: 1
return try {
val response = client.get("$baseUrl/users", query = mapOf(
"page" to page.toString(),
"limit" to params.loadSize.toString()
))
val users = response.jsonList<User>()
LoadResult.Page(
data = users,
prevKey = if (page == 1) null else page - 1,
nextKey = if (users.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
return state.anchorPosition?.let { pos ->
state.closestPageToPosition(pos)?.prevKey?.plus(1)
?: state.closestPageToPosition(pos)?.nextKey?.minus(1)
}
}
}
// W ViewModel
val usersPager = Pager(PagingConfig(pageSize = 20)) {
UserPagingSource(client)
}.flow.cachedIn(viewModelScope)
class InfiniteScrollHelper<T>(
private val pageSize: Int = 20,
private val loadPage: suspend (page: Int) -> List<T>
) {
private var currentPage = 1
private var isLoading = false
private var hasMore = true
private val _items = MutableStateFlow<List<T>>(emptyList())
val items: StateFlow<List<T>> = _items
private val _state = MutableStateFlow<LoadState>(LoadState.Idle)
val state: StateFlow<LoadState> = _state
suspend fun loadInitial() {
currentPage = 1
_items.value = emptyList()
loadMore()
}
suspend fun loadMore() {
if (isLoading || !hasMore) return
isLoading = true
_state.value = LoadState.Loading
try {
val newItems = loadPage(currentPage)
hasMore = newItems.size >= pageSize
_items.update { it + newItems }
currentPage++
_state.value = LoadState.Idle
} catch (e: Exception) {
_state.value = LoadState.Error(e.message ?: "Error")
} finally {
isLoading = false
}
}
}
sealed class LoadState {
object Idle : LoadState()
object Loading : LoadState()
data class Error(val message: String) : LoadState()
}