Creating Custom Paging for Infinite Scrolling in Jetpack Compose

Shadman Adman
3 min readJun 29, 2024

Infinite scrolling is a common feature in modern mobile applications, allowing users to continuously scroll through a list of items without needing to navigate between pages. This article demonstrates how to implement custom paging for infinite scrolling in Jetpack Compose using a LazyColumn and a footer with a progress indicator.

Composable Function: CustomPagingColumn

Here’s the full code of the composable function we’ll break down:

@Composable
fun CustomPagingColumn(
referralList: List<MarketingReferralsModel.CryptData.Item>,
perPage: Int,
isLoading: Boolean,
onLoadMoreItem: () -> Unit
) {
var previousItemCount by remember { mutableStateOf(perPage) }
val scrollState = rememberLazyListState()

// Observe the scroll position
LaunchedEffect(scrollState) {
snapshotFlow { scrollState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 }
.collect { lastVisibleIndex ->
val totalItems = scrollState.layoutInfo.totalItemsCount
if (totalItems - lastVisibleIndex <= 2 && totalItems >= previousItemCount) {
previousItemCount = totalItems
onLoadMoreItem()
}
}
}

LazyColumn(state = scrollState) {
// Items
items(referralList) {
ReferralsItem(it)
}

// Footer
if (isLoading)
item {
Box(
modifier = Modifier.fillMaxWidth().padding(top = 20.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp), color = AppInfoManager.getAccentColor()
)
}
}
}
}

Breakdown of the Code

fun CustomPagingColumn(
referralList: List<MarketingReferralsModel.CryptData.Item>,
perPage: Int,
isLoading: Boolean,
onLoadMoreItem: () -> Unit
)

Function Parameters

  • referralList: The list of items to display.
  • perPage: The number of items to load per page.
  • isLoading: A boolean indicating whether more items are currently being loaded.
  • onLoadMoreItem: A lambda function that is triggered to load more items when the user scrolls to the end of the list.

State Variables

var previousItemCount by remember { mutableStateOf(perPage) }
val scrollState = rememberLazyListState()
  • previousItemCount: Keeps track of the number of items loaded previously. Initialized to perPage.
  • scrollState: Holds the state of the LazyColumn, including information about currently visible items.

Infinite Scrolling Logic

LaunchedEffect(scrollState) {
snapshotFlow { scrollState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 }
.collect { lastVisibleIndex ->
val totalItems = scrollState.layoutInfo.totalItemsCount
if (totalItems - lastVisibleIndex <= 2 && totalItems >= previousItemCount) {
previousItemCount = totalItems
onLoadMoreItem()
}
}
}
  • LaunchedEffect: A side-effect that runs a block of code in a coroutine when scrollState changes.
  • snapshotFlow: Converts the current state of scrollState to a flow.
  • collect: Observes changes to the last visible item index.
  • Logic: When the user scrolls near the end of the list (totalItems - lastVisibleIndex <= 2), it triggers onLoadMoreItem() to load more items.

Using the CustomPagingColumn Composable

Here’s how you can use the CustomPagingColumn composable in your application, including state management for infinite scrolling:

@Composable
fun ReferralsScreen(component: ReferralsComponent) {
var isRefreshing by remember { mutableStateOf(false) }
var dataPage by remember { mutableStateOf(1) }
var pageCount by remember { mutableStateOf(PER_PAGE) }
var isDataRichesEnd by remember { mutableStateOf(false) }
val referralsList = remember { mutableStateListOf<MarketingReferralsModel.CryptData.Item>() }

LaunchedEffect(key1 = dataPage) {
if (!isDataRichesEnd) {
component.marketingViewModel.marketingReferrals(dataPage)
}
}

component.marketingViewModel.marketingReferrals.collectAsState().value.let { loadable ->
isRefreshing = loadable == Loadable.Loading
if (loadable is Loadable.Success) {
referralsList.addAll(loadable.data.cryptData.items)
pageCount = loadable.data.cryptData.count
isDataRichesEnd = loadable.data.cryptData.items.isEmpty()
component.marketingViewModel.resetMarketingReferrals()
}
}

Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors().grayFBFBFB)
) {
CustomPagingColumn(
referralList = referralsList,
perPage = pageCount,
isLoading = isRefreshing,
onLoadMoreItem = {
if (!isRefreshing) dataPage++
}
)
}
}

Explanation

  1. State Management:
var isRefreshing by remember { mutableStateOf(false) }
var dataPage by remember { mutableStateOf(1) }
var pageCount by remember { mutableStateOf(PER_PAGE) }
var isDataRichesEnd by remember { mutableStateOf(false) }
val referralsList = remember { mutableStateListOf<MarketingReferralsModel.CryptData.Item>() }
  • isRefreshing: Indicates whether more items are currently being loaded.
  • dataPage: Keeps track of the current page.
  • pageCount: Number of items to load per page.
  • isDataRichesEnd: Indicates whether there are no more items to load.
  • referralsList: Holds the list of referral items.

2. Loading Items:

LaunchedEffect(key1 = dataPage) {
if (!isDataRichesEnd) {
component.marketingViewModel.marketingReferrals(dataPage)
}
}

Triggers data loading whenever dataPage changes, unless isDataRichesEnd is true.

3. Collecting and Updating State:

component.marketingViewModel.marketingReferrals.collectAsState().value.let { loadable ->
isRefreshing = loadable == Loadable.Loading
if (loadable is Loadable.Success) {
referralsList.addAll(loadable.data.cryptData.items)
pageCount = loadable.data.cryptData.count
isDataRichesEnd = loadable.data.cryptData.items.isEmpty()
component.marketingViewModel.resetMarketingReferrals()
}
}
  • Observes the marketingReferrals state and updates the local state accordingly.
  • Adds new items to referralsList and updates isDataRichesEnd based on whether new items are loaded.
  • At the end, we reset the marketingReferrals state to Loading in our view model

4. Using CustomPagingColumn:

CustomPagingColumn(
referralList = referralsList,
perPage = pageCount,
isLoading = isRefreshing,
onLoadMoreItem = {
if (!isRefreshing) dataPage++
}
)
  • Passes the current state and data to the CustomPagingColumn composable.
  • Increments dataPage to load more items when the user scrolls to the end of the list.

By understanding and utilizing these components, you can create a smooth infinite scrolling experience in your Jetpack Compose applications.

--

--