Skip to content

A library that provides several functionalities to make it easy to write a list with Jetpack Paging3

License

Notifications You must be signed in to change notification settings

KevinnZou/compose-pagingList

Repository files navigation

Compose Paging List for Jetpack Compose

A library that provides several functionalities to make it easy to write a list with paging data

  1. It defines methods to config Google's Jetpack Paging Library easily.
  2. It provides default layouts for different paging loading status.
  3. It provides some composable layouts that can be used to form a paging list easily.

Usage

To start using this library, apps need to define pager data and provide it to the list

Pager Data

Apps can define the pager data for list in the ViewModel in several ways

  1. The simplest way is to use the easyPager. It requires the apps wrap the data in PagingListWrapper which need the data list and the hasMore sign.
@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)
    }
}
  1. However, the first method is only suitable for the simple data model. If you have a complex model, then you can use the easyPager2
val pager2 = easyPager2(initialKey = 0){
    return@easyPager2 loadData2(it)
}

private suspend fun loadData2(page: Int): PageListVO {
    delay(1500)
    val data = mutableListOf("Page $page")
    repeat(20) {
        data.add("Item $it")
    }
    return PageListVO(page, data, page < 2)
}

It requires the data model to implement the interface IHasMoreListVO to provide the list data, hasMore sign, preKey and the nextKey.

data class PageListVO(var page: Int, var items: MutableList<String>, var hasMore: Boolean) : IHasMoreListVO<Int,String> {
    override fun hasMore(): Boolean {
        return hasMore
    }

    override fun getList(): List<String> {
        return items
    }

    override fun getPreKey(): Int? {
        return if (page - 1 < 0) null else page - 1
    }

    override fun getNextKey(): Int? {
        return page + 1
    }
}
  1. Lastly, the library provides the pager which allows the apps to map their own defined Http Result to Kotlin Standard Result. Then it will map it to PagingSource.LoadResult automatically.
pager(pagerConfig, initialKey) { key ->
    when (val result = loadData(key)) {
        is HttpResult.Error.BusinessError -> {
            return@pager Result.failure(Exception("${result.code} - ${result.message}"))
        }
        is HttpResult.Error.NetworkError -> {
            return@pager Result.failure(result.error)
        }
        is HttpResult.Success -> {
            return@pager Result.success(result.response)
        }
        else -> {
            return@pager Result.failure(Exception("Other Exception"))
        }
    }
}

List

This library provides several ways to write a paging list

  1. The easiest way is to use PagingLazyColumn directly
@Composable
fun EasyPagingListScreen(viewModel: MainViewModel = hiltViewModel()) {
    val pagerData = viewModel.pager.collectAsLazyPagingItems()
    PagingLazyColumn(pagingData = pagerData) { _, value ->
        Text(value)
    }
}
  1. However, the method above restricts the things that apps must use a lazyColumn and cannot add header. Thus, we can use the method below to compose the list you want which also has the same funtionality
@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)
        }
    }
}
  1. The second method Wrap the LazyRow with PagingListContainer which provides the first loading status, if you want to control it by yourself, you can directly use the LazyList with itemPaging
@Composable
fun RawPagingListScreen(viewModel: MainViewModel = hiltViewModel()) {
    val pagerData = viewModel.pager.collectAsLazyPagingItems()
    // show first loading status
    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)
    }
}

GridList

This library also add support for the GridList, you can use it like this:

@Composable
fun PagingGridScreen(viewModel: MainViewModel = hiltViewModel()) {
    val pagerData = viewModel.pager.collectAsLazyPagingItems()
    PagingListContainer(pagingData = pagerData) {
        LazyVerticalGrid(
            columns = GridCells.Fixed(2),
            verticalArrangement = Arrangement.spacedBy(10.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp),
            contentPadding = PaddingValues(10.dp)
        ) {
            itemsIndexed(pagerData) { index, value ->
                Box(
                    Modifier
                        .background(if (index.rem(2) == 0) Color.Yellow else Color.Magenta)
                        .height(128.dp),
                    contentAlignment = Alignment.Center
                ) {
                    Text(text = value.toString())
                }
            }
            if (pagerData.itemCount.rem(2) != 0) {
                item { Spacer(Modifier.background(Color.White)) }
            }
            itemPaging(pagerData, 2)
        }
    }
}

Customization

Apps can customize the loading, no more, error content and so on.

@Composable
fun <T : Any> PagingLazyColumn(
    modifier: Modifier = Modifier,
    pagingData: LazyPagingItems<T>,
    loadingContent: @Composable (() -> Unit)? = { DefaultLoadingContent() },
    noMoreContent: @Composable (() -> Unit)? = { DefaultNoMoreContent() },
    errorContent: @Composable ((retry: (() -> Unit)?) -> Unit)? = { retry ->
        DefaultErrorContent(
            retry
        )
    },
    refreshingContent: @Composable (() -> Unit)? = { DefaultRefreshingContent() },
    firstLoadErrorContent: @Composable ((retry: (() -> Unit)?) -> Unit)? = { retry ->
        DefaultFirstLoadErrorContent(
            retry
        )
    },
    emptyListContent: @Composable (() -> Unit)? = { DefaultEmptyListContent() },
    pagingItemContent: @Composable (index: Int, value: T?) -> Unit,
)
@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()
    }
}

Download

The Current Release Version is 1.1.0. For future release, please refer to the release session of the github repository.

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.github.kevinnzou:compose-paginglist:<version>")
}

License

Compose PagingList is distributed under the terms of the Apache License (Version 2.0). See the license for more information.