Effortlessly Implement List Pagination in Jetpack Compose with our Intuitive Paging Library

Kevin Zou
5 min readAug 6, 2023

--

Background

Google has provided a new Paging3 Library to support the List Pagination in Jetpack Compose. It makes it easier to use paging data with Compose List. However, it is still too complex to use. It requires many configurations and template codes during usage. Furthermore, for different loading statuses, it requires developers to provide the layouts and map them to the loading status manually. For all these drawbacks, I decided to write a library that can make the developers implement the paging list in Jetpack Compose much easier.

Jetpack Paging3

Before introducing my library, let me show you what we need to do to construct a paging list with Jetpack Paging3 Library.

First, we need to make some basic configurations, like initialLoadSize, prefetchDistance, maxSize, and so on. Also, we need to provide a pagingSourceFactory for Pager which decide how to load paging data.

class ComposePagingViewModel @Inject constructor() : ViewModel() {
private val appConfig = AppPagingConfig()
private val pagerConfig = PagingConfig(
appConfig.pageSize,
initialLoadSize = appConfig.initialLoadSize,
prefetchDistance = appConfig.prefetchDistance,
maxSize = appConfig.maxSize,
enablePlaceholders = appConfig.enablePlaceholders
)
val pager by lazy {
Pager(
config = pagerConfig,
initialKey = 0,
pagingSourceFactory = {
object : PagingSource<Int, String>() {
override fun getRefreshKey(state: PagingState<Int, String>): Int {
return 0
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
val response = try {
loadData(params)
} catch (exception: Exception) {
return@basePager PagingSource.LoadResult.Error(exception)
}
val page = params.key ?: 0
return LoadResult.Page(
response,
prevKey = if (page - 1 < 0) null else page - 1,
nextKey = if (page > 5) null else page + 1
)
}
}
}
).flow.cachedIn(viewModelScope)
}

Furthermore, we need to display different layouts composable for different loading statuses. You can see that there will have 6 statuses that need to be considered and provided the layout.

if (pagerData.loadState.refresh is LoadState.Loading) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Loading")
}
} else if (pagerData.loadState.refresh is LoadState.Error) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Loading Error")
}
} else {
LazyColumn {
itemsIndexed(pagerData) { index, value ->
Text(
text = "Index=$index $value",
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
textAlign = TextAlign.Center
)
}
if (pagerData.loadState.append is LoadState.Loading) {
item {
Text(
text = "Loading",
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
textAlign = TextAlign.Center
)
}
}else if (pagerData.loadState.append is LoadState.Error) {
item {
Text(
text = "Loading Error",
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
textAlign = TextAlign.Center
)
}
}
}

}

Plan

You can see that it requires too many configurations and template codes for us to use Jetpack Paging3 Library to support the paging list in Jetpack Compose. Thus, I decided to write a library that has the following features to make it effortless to implement the paging list in Jetpack Compose.

  1. Implementing a Kotlin extension function to provide the basic configuration for Jetpack Paging3 Library.
  2. Providing the default layouts for different loading statuses and matching them automatically.
  3. Designing a composable function that wraps all logic and only requires the developers to provide the loading data code.

compose-pagingList

As a result, I published a support library on the maven and made it open-sourced on Github: compose-pagingList

Normal Situation
Loading Error

Usage

With this library, you can develop the paging list in Jetpack Compose easily.

You just need to define a pager with easyPager and provide the load data logic to it. It will do the configuration process for you automatically.

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
val pager = easyPager {
loadData(it)
}

private suspend fun loadData(page: Int): PagingListWrapper<String> {
delay(2000)
val data = mutableListOf("Page $page")
repeat(20) {
data.add("Item $it")
}
return PagingListWrapper(data, page < 3)
}
}

Then, you just need to use the provided PagingLazyColumn to display a lazy column with paging data.

@Composable
fun EasyPagingListScreen(viewModel: MainViewModel = hiltViewModel()) {
val pagerData = viewModel.pager.collectAsLazyPagingItems()
PagingLazyColumn(pagingData = pagerData) { _, value ->
Text(value)
}
}

Also, it supports developers to use other list types and add headers before the data items.

@Composable
fun RawPagingListScreen(viewModel: MainViewModel = hiltViewModel()) {
val pagerData = viewModel.pager.collectAsLazyPagingItems()
// show first loading status
PagingListContainer(pagingData = pagerData) {
LazyRow {
item {
Text(
text = "Raw PagingList",
modifier = Modifier
.height(40.dp)
.fillParentMaxWidth()
.padding(top = 15.dp),
textAlign = TextAlign.Center
)
}
itemsIndexed(pagerData) { _, value ->
PagingContent(value)
}
// show load more status
itemPaging(pagerData)
}
}
}

Furthermore, besides the default layouts, it supports the customization of layouts for different loading statuses.

@Composable
fun CustomLoadMoreScreen(viewModel: MainViewModel = hiltViewModel()) {
val pagerData = viewModel.pager.collectAsLazyPagingItems()
PagingLazyColumn(
pagingData = pagerData,
loadingContent = { CustomLoadMoreContent() },
refreshingContent = { CustomRefreshingContent() }) { _, value ->
PagingContent(value)
}
}

@Composable
fun CustomLoadMoreContent() {
Row(
modifier = Modifier
.fillMaxWidth()
.height(40.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
LinearProgressIndicator(
color = Color.Red,
modifier = Modifier
.width(100.dp)
.height(2.dp),
)
}
}

@Composable
fun CustomRefreshingContent() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CustomLoadMoreContent()
}
}
Customized loading UI

Conclusion

The reason why I wrote this library at the beginning was that when I used the Paging3 library in Compose, I felt that it was too cumbersome and there were too many sample codes. Especially when it comes to simple paging lists, each list needs to write a lot of repetitive code. So, I want to wrap a paginated list library that can be used out of the box. At present, the library has been open-sourced on GitHub and released to version 1.1.0, which supports the paging display of GridView. Suggestions for improvement are welcome.

--

--

Kevin Zou

Android Developer in NetEase. Focusing on Kotlin Multiplatform and Compose Multiplatform