Skip to content

Instantly share code, notes, and snippets.

@bhuvan0616
Created July 6, 2022 12:07
Show Gist options
  • Save bhuvan0616/6ef4c01b8569efe14adefc99598ea62f to your computer and use it in GitHub Desktop.
Save bhuvan0616/6ef4c01b8569efe14adefc99598ea62f to your computer and use it in GitHub Desktop.
compose pagination
sealed class LoadingState {
object NotLoading : LoadingState()
object Loading : LoadingState()
object Refreshing : LoadingState()
data class Error(val error: ErrorState) : LoadingState()
}
data class PageInfo<K, T>(
val nextKey: K,
val data: List<T>,
)
data class Pagination<T>(
val items: List<T>,
val loadingState: LoadingState,
val pageEnded: Boolean,
) {
companion object {
fun <T> loading(items: List<T>): Pagination<T> {
return Pagination(
items = items,
loadingState = LoadingState.Loading,
pageEnded = false
)
}
fun <T> data(items: List<T>, pageEnded: Boolean): Pagination<T> {
return Pagination(
items = items,
loadingState = LoadingState.NotLoading,
pageEnded = pageEnded
)
}
fun <T> error(items: List<T>, error: ErrorState): Pagination<T> {
return Pagination(
items = items,
loadingState = LoadingState.Error(error),
pageEnded = true
)
}
fun <T> empty() = Pagination<T>(
items = emptyList(),
loadingState = LoadingState.Refreshing,
pageEnded = false
)
}
}
@Suppress("FunctionName")
fun <K, T> ViewModel.PageLoader(
pageSize: Int,
paginationSource: PaginationSource<K, T>,
loadOnInitialization: Boolean = true,
page: (pagination: Pagination<T>) -> Unit,
): PageLoaderImpl<K, T> {
return PageLoaderImpl(
pageSize = pageSize,
scope = viewModelScope,
paginationSource = paginationSource,
page = page
).also {
if (loadOnInitialization) it.loadPage()
}
}
class PageLoaderImpl<K, T>(
private val pageSize: Int,
private val scope: CoroutineScope,
private val paginationSource: PaginationSource<K, T>,
private val page: (pagination: Pagination<T>) -> Unit,
) {
private var nextKey: K? = null
private var items: List<T> = emptyList()
fun loadPage() {
if (nextKey == null) {
page(Pagination.empty())
} else {
page(Pagination.loading(items))
}
scope.launch {
page(
when (val response = paginationSource.onLoad(nextKey)) {
is Response.Success -> {
nextKey = response.data.nextKey
items = items + response.data.data
Pagination.data(items, response.data.data.size < pageSize)
}
is Response.Failure -> {
Pagination.error(items, response.error.toErrorState())
}
}
)
}
}
fun refresh() {
nextKey = null
items = emptyList()
loadPage()
}
}
abstract class PaginationSource<K, T>() {
abstract suspend fun onLoad(key: K?): Response<PageInfo<K, T>>
}
fun <T> LazyListScope.items(
pagination: Pagination<T>,
nextPage: () -> Unit,
itemContent: @Composable LazyItemScope.(item: T) -> Unit
) {
items(count = pagination.items.size) { index ->
itemContent(pagination.items[index])
if (index == pagination.items.lastIndex
&& pagination.loadingState is LoadingState.NotLoading
&& !pagination.pageEnded
) {
nextPage()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment