0Pricing
Android Academy · Lesson

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 load
  • append - 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.Loading

Full-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; handle null placeholders
  • Use itemKey { it.id } for stable keys
  • loadState.refresh/append/prepend drive spinners, errors and empty states
  • retry() and refresh() 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

  1. Why Paging
  2. PagingSource and Pager
  3. Paging in Compose Lists
  4. RemoteMediator and Caching
← Back to Android Academy