Paging in Compose Lists
Render paged data with LazyColumn.
Paging in Compose Lists is a free Android Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Rendering Paged Data in Compose
The data layer hands you a Flow<PagingData<Article>>. The paging-compose library turns that flow into something a LazyColumn can render, complete with on-demand loading as the user scrolls.
The key bridge is collectAsLazyPagingItems().
collectAsLazyPagingItems
Call collectAsLazyPagingItems() on the flow inside a composable. It returns a LazyPagingItems object that tracks the loaded items and load states, recomposing the UI as pages arrive.
import androidx.paging.compose.collectAsLazyPagingItems
@Composable
fun ArticleScreen(viewModel: ArticleViewModel) {
val articles = viewModel.articles.collectAsLazyPagingItems()
ArticleList(articles)
}items() with LazyPagingItems
Inside a LazyColumn, use the Paging items() overload. It reads from LazyPagingItems and triggers loading of the next page when the user nears the end.
Each item may be null when placeholders are enabled, so handle that case.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
@Composable
fun ArticleList(articles: LazyPagingItems<Article>) {
LazyColumn {
items(articles) { article ->
if (article != null) {
ArticleRow(article)
}
}
}
}Stable Keys for Items
Give each row a stable key so Compose can track items efficiently across page loads and avoid unnecessary recomposition.
Use the Paging itemKey helper with a unique, stable property such as the item id.
import androidx.paging.compose.itemKey
LazyColumn {
items(
count = articles.itemCount,
key = articles.itemKey { it.id }
) { index ->
val article = articles[index]
if (article != null) ArticleRow(article)
}
}Reading Load States
LazyPagingItems.loadState exposes the current loading and error state for three operations:
refresh- the initial or pull-to-refresh loadappend- loading the next page (scrolling down)prepend- loading the previous page (scrolling up)
import androidx.paging.LoadState
val refresh = articles.loadState.refresh
val append = articles.loadState.append
val isInitialLoading = refresh is LoadState.Loading
val isLoadingMore = append is LoadState.LoadingFull-Screen Loading and Empty States
Use the refresh state to drive the first impression: a centered spinner while loading, an error view on failure, or an empty message when there is nothing to show.
when (val refresh = articles.loadState.refresh) {
is LoadState.Loading -> FullScreenSpinner()
is LoadState.Error -> FullScreenError(
message = refresh.error.message,
onRetry = { articles.retry() }
)
is LoadState.NotLoading -> {
if (articles.itemCount == 0) EmptyState()
else ArticleList(articles)
}
}Footer Spinner While Appending
To show a small spinner at the bottom while the next page loads, add an extra item to the LazyColumn based on the append load state.
LazyColumn {
items(count = articles.itemCount, key = articles.itemKey { it.id }) { index ->
articles[index]?.let { ArticleRow(it) }
}
if (articles.loadState.append is LoadState.Loading) {
item {
Box(Modifier.fillMaxWidth().padding(16.dp), Alignment.Center) {
CircularProgressIndicator()
}
}
}
}Retry on Error
When an append fails, show a retry row at the bottom. Calling articles.retry() re-attempts only the failed load - it does not reload the whole list.
val append = articles.loadState.append
if (append is LoadState.Error) {
item {
Row(Modifier.fillMaxWidth().padding(16.dp), Arrangement.Center) {
Text("Couldn't load more")
Spacer(Modifier.width(8.dp))
Button(onClick = { articles.retry() }) { Text("Retry") }
}
}
}Pull to Refresh
Trigger a fresh load with articles.refresh(). Combine it with Material 3's pull-to-refresh, driving the indicator from the refresh load state.
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
val isRefreshing = articles.loadState.refresh is LoadState.Loading
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { articles.refresh() }
) {
ArticleList(articles)
}Header and Separators
You can freely mix paged items with non-paged content in the same LazyColumn. Add a sticky header, a search bar, or section separators around the paged items() call.
Because everything lives in one LazyColumn, scrolling stays unified and smooth.
LazyColumn {
item { SearchBar(query, onQueryChange) }
items(count = articles.itemCount, key = articles.itemKey { it.id }) { index ->
val article = articles[index]
if (article != null) {
ArticleRow(article)
HorizontalDivider()
}
}
if (articles.loadState.append.endOfPaginationReached) {
item { Text("You're all caught up", Modifier.padding(16.dp)) }
}
}Previewing Paged Lists
For Compose previews and tests you do not need a real network. Wrap sample data in PagingData.from(...) and expose it as a flow.
This lets you build and preview your list UI in isolation.
import androidx.paging.PagingData
import kotlinx.coroutines.flow.flowOf
@Preview
@Composable
fun ArticleListPreview() {
val sample = listOf(Article(1, "Hello"), Article(2, "World"))
val flow = flowOf(PagingData.from(sample))
ArticleList(flow.collectAsLazyPagingItems())
}Quick Check
Which function converts a Flow<PagingData> into something a Compose LazyColumn can render?
Recap: Paging in Compose
You rendered paged data in Jetpack Compose:
collectAsLazyPagingItems()bridges the flow to the UI- The Paging
items()overload loads more as you scroll; handlenullplaceholders - Use
itemKey { it.id }for stable keys loadState.refresh/append/prependdrive spinners, errors and empty statesretry()andrefresh()recover from failures and reload
Next: combining network and database with RemoteMediator.
Frequently asked questions
Is the “Paging in Compose Lists” lesson free?
Yes — the full text of “Paging in Compose Lists” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “Paging in Compose Lists”?
Render paged data with LazyColumn. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Paging in Compose Lists” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Why Paging
- PagingSource and Pager
- Paging in Compose Lists
- RemoteMediator and Caching