Overview
Pagination is one area where GraphQL shines — but it can be a bit tricky at first. Let's take a closer look! 👀
In this lesson, we will:
- Use the
cursor
variable to load new sets of data - Implement the app logic to programmatically load more data
Paginating the list of launches
As you might have noticed, the object returned from the LaunchListQuery
is a LaunchConnection
. This object has a list of launches, a pagination cursor, and a boolean to indicate whether more launches exist.
type LaunchConnection {cursor: String!hasMore: Boolean!launches: [Launch]!}
When using a cursor-based pagination system, it's important to remember that the cursor gives you a place where you can get all results after a certain spot, regardless of whether more items have been added in the interim.
In an earlier lesson, we hardcoded the SMALL
size argument directly in the GraphQL query, but we can also define arguments programmatically using variables. We'll use them here to implement pagination.
Add a cursor
variable
In LaunchList.graphql
, add a cursor
variable. In GraphQL, variables are prefixed with the dollar sign.
Also add the cursor
and hasMore
fields to the query, as we will use them to paginate:
query LaunchList($cursor: String) {launches(after: $cursor) {cursorhasMorelaunches {idsitemission {namemissionPatch(size: SMALL)}}}}
We can return to the Variables panel in Explorer to test this out. If we omit the $cursor
variable, the server returns data starting from the beginning. Try it out:
Now let's make sure our app's views are making use of this cursor
.
Paginate the list of launches
Declare and remember a cursor
var, initialized to null
, and add it as a key to LaunchedEffect
. That way, the query will be re-executed every time the cursor
changes.
Also keep a reference to response
so we can access the hasMore
and cursor
fields further down.
@Composablefun LaunchList(onLaunchClick: (launchId: String) -> Unit) {var cursor: String? by remember { mutableStateOf(null) }var response: ApolloResponse<LaunchListQuery.Data>? by remember { mutableStateOf(null) }var launchList by remember { mutableStateOf(emptyList<LaunchListQuery.Launch>()) }LaunchedEffect(cursor) {
Pass the cursor
to the LaunchListQuery
, and add a special item at the end of the list which updates the cursor
if hasNext
is true. This will trigger a new query with the new cursor whenever the user scrolls to the end of the list, and launchList
will be concatenated with the new results.
Note: this is a basic implementation of pagination in Compose. In a production project you may use something more advanced, like the Jetpack Paging library.
The whole function should look like this:
@Composablefun LaunchList(onLaunchClick: (launchId: String) -> Unit) {var cursor: String? by remember { mutableStateOf(null) }var response: ApolloResponse<LaunchListQuery.Data>? by remember { mutableStateOf(null) }var launchList by remember { mutableStateOf(emptyList<LaunchListQuery.Launch>()) }LaunchedEffect(cursor) {response = apolloClient.query(LaunchListQuery(Optional.present(cursor))).execute()launchList = launchList + response?.data?.launches?.launches?.filterNotNull().orEmpty()}LazyColumn {items(launchList) { launch ->LaunchItem(launch = launch, onClick = onLaunchClick)}item {if (response?.data?.launches?.hasMore == true) {LoadingItem()cursor = response?.data?.launches?.cursor}}}}
Note that we wrap the cursor
in an Optional
: this is because this parameter can be omitted in the query.
Test (simplified) pagination
Click Run. You can now see all SpaceX launches back to their first FalconSat from Kwajalein Atoll!
You might notice that this pagination is happening via a pattern called "infinite scrolling," but many applications choose to show the user page numbers or "tap to load more." You can implement any of these patterns with Apollo Kotlin and GraphQL.
Up next
Let's wrap up this course by implementing a notification feature when we book a seat on a launch. Full steam ahead to subscriptions!
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.