loading...
Cover image for Implementing Paging in Jetpack Compose

Implementing Paging in Jetpack Compose

kriticalflare profile image kriticalflare ・3 min read

Jetpack Compose Alpha06 was recently released and along with it, came the new paging-compose library. The library provides integration between the Paging 3.0 library and Jetpack Compose.

Both Paging and Jetpack Compose are in alpha rightnow so expect the API to have changes in near future.

This article will be a short one focused more on integrating paging with compose rather than specifics of paging 3.0 itself.

The code snippets used in this article are from

GitHub logo kriticalflare / Rick-Morty-Compose

Android app using Jetpack Compose and Rick and Morty API

Adding the dependencies

Add the following dependencies to an existing Jetpack Compose project or create a new project using the compose template from the latest Android Studio Canary.

app/build.gradle


     def paging_compose_version = "1.0.0-alpha01"
     implementation "androidx.paging:paging-compose:$paging_compose_version"

Enter fullscreen mode Exit fullscreen mode

Setting up the Data Source

One can setup the paging data source for jetpack compose in the same fashion as one does for the legacy view based world.

class CharactersPaging {

    fun getCharacters(): PagingSource<Int, Character> {
        return object : PagingSource<Int, Character>() {
            override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Character> {
                val pageNumber = params.key ?: 0

                val charactersResponse = RickMortyClient.INSTANCE.getAllCharacters(page = pageNumber)
                val characters = charactersResponse.results

                val prevKey = if (pageNumber > 0) pageNumber - 1 else null
                val nextKey = if (charactersResponse.info.next != null) pageNumber + 1 else null

                return LoadResult.Page(
                    data = characters,
                    prevKey = prevKey,
                    nextKey = nextKey
                )
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

I have used the Rick And Morty API for this example. The API gives us an Info object in response which provides information about the previous and next pages (if they exist).

Connecting the data source to the UI

The library comes with special extension functions on the LazyList Scope used by the lazy column and row composables which makes declarative lists very easy to create.

We create a Pager which provides us a flow of PagingData backed by the data source we built earlier.

val charactersPaging = remember {
                CharactersPaging()
            }
val pager = remember {
                Pager(
                    PagingConfig(
                        pageSize = 20,
                        enablePlaceholders = true,
                    )
                ) {                  
                 charactersPaging.getCharacters()
                }
            }
Enter fullscreen mode Exit fullscreen mode

This flow can then be converted to the lazyPagingItems using the extensions from paging compose as follows.

val lazyPagingItems: LazyPagingItems<Character> = pager.flow.collectAsLazyPagingItems()
Enter fullscreen mode Exit fullscreen mode

These lazyPagingItems can be then passed to the items and itemsIndexed extension methods on the LazyList Scope. Android Studio has trouble importing the correct extension methods in this case, so make sure to add these imports manually if you face any issues.

import androidx.paging.compose.items
import androidx.paging.compose.itemsIndexed
Enter fullscreen mode Exit fullscreen mode

Now in typical declarative ui fashion, we can emit different composables depending on different loadStates.

LazyColumn{
                if (lazyPagingItems.loadState.refresh == LoadState.Loading) {
                    item {
                        Column(
                            modifier = Modifier.fillParentMaxSize(),
                            verticalArrangement = Arrangement.Center,
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                            CircularProgressIndicator()
                        }
                    }
                }

                itemsIndexed(lazyPagingItems) { index, item ->
                    if (item != null) {
                        CharacterItem(
                            modifier = Modifier.padding(8.dp),
                            character = item,
                            onClick = onCharSelect
                        )
                    }
                }

                if (lazyPagingItems.loadState.append == LoadState.Loading) {
                    item {
                        CircularProgressIndicator(
                            modifier = Modifier.fillMaxWidth()
                                .wrapContentWidth(Alignment.CenterHorizontally)
                        )
                    }
                }
          }
Enter fullscreen mode Exit fullscreen mode

The final implementation

@ExperimentalLazyDsl
@Composable
fun CharacterScreen(
    modifier: Modifier = Modifier.fillMaxSize(),
    onCharSelect: (Int) -> Unit
) {
    Surface {
        Box(
            modifier = modifier,
        ) {
            val charactersPaging = remember {
                CharactersPaging()
            }
            val pager = remember {
                Pager(
                    PagingConfig(
                        pageSize = 20,
                        enablePlaceholders = true,
                    )
                ) {                  charactersPaging.getCharacters()
                }
            }

            val lazyPagingItems: LazyPagingItems<Character> = pager.flow.collectAsLazyPagingItems()

            LazyColumn{
                if (lazyPagingItems.loadState.refresh == LoadState.Loading) {
                    item {
                        Column(
                            modifier = Modifier.fillParentMaxSize(),
                            verticalArrangement = Arrangement.Center,
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                            CircularProgressIndicator()
                        }
                    }
                }

                itemsIndexed(lazyPagingItems) { index, item ->
                    if (item != null) {
                        CharacterItem(
                            modifier = Modifier.padding(8.dp),
                            character = item,
                            onClick = onCharSelect
                        )
                    }
                }

                if (lazyPagingItems.loadState.append == LoadState.Loading) {
                    item {
                        CircularProgressIndicator(
                            modifier = Modifier.fillMaxWidth()
                                .wrapContentWidth(Alignment.CenterHorizontally)
                        )
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Its pretty clear that Jetpack Compose makes implementation of Paging 3.0 much simpler than the legacy view system. You can check out the project used in this article for more examples integrating Paging, Navigation Component, Edge to Edge UI and much more

GitHub logo kriticalflare / Rick-Morty-Compose

Android app using Jetpack Compose and Rick and Morty API

Thank you for reading! You can get in touch with me here on Dev , Github or Linkedin

Discussion

pic
Editor guide