7. Paginate results


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.

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 the previous section, you hardcoded the SMALL size argument directly in the GraphQL query, but you can also define arguments programmatically using variables. You will use them here to implement pagination.

Add a cursor variable, and the cursor and hasMore fields

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:

GraphQL
app/src/main/graphql/LaunchList.graphql
1query LaunchList($cursor: String) {
2  launches(after: $cursor) {
3    cursor
4    launches {
5      id
6      site
7      mission {
8        name
9        missionPatch(size: SMALL)
10      }
11    }
12    hasMore
13  }
14}

You can experiment with GraphQL variables in Sandbox Explorer by using the pane under the main body of the operation named Variables. If you omit the $cursor variable, the server returns data starting from the beginning:

Explorer variables

Fetch the next page when reaching the end of the list

Declare and remember a cursor var, initialized to null, and make the LauchedEffect depend on it. 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.

Kotlin
app/src/main/java/com/example/rocketreserver/LaunchList.kt
1@Composable
2fun LaunchList(onLaunchClick: (launchId: String) -> Unit) {
3    var cursor: String? by remember { mutableStateOf(null) }
4    var response: ApolloResponse<LaunchListQuery.Data>? by remember { mutableStateOf(null) }
5    var launchList by remember { mutableStateOf(emptyList<LaunchListQuery.Launch>()) }
6    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 real project you may use something more advanced, like the Jetpack Paging library.

The whole function should look like this:

Kotlin
app/src/main/java/com/example/rocketreserver/LaunchList.kt
1@Composable
2fun LaunchList(onLaunchClick: (launchId: String) -> Unit) {
3    var cursor: String? by remember { mutableStateOf(null) }
4    var response: ApolloResponse<LaunchListQuery.Data>? by remember { mutableStateOf(null) }
5    var launchList by remember { mutableStateOf(emptyList<LaunchListQuery.Launch>()) }
6    LaunchedEffect(cursor) {
7        response = apolloClient.query(LaunchListQuery(Optional.present(cursor))).execute()
8        launchList = launchList + response?.data?.launches?.launches?.filterNotNull().orEmpty()
9    }
10    
11    LazyColumn {
12        items(launchList) { launch ->
13            LaunchItem(launch = launch, onClick = onLaunchClick)
14        }
15        item {
16            if (response?.data?.launches?.hasMore == true) {
17                LoadingItem()
18                cursor = response?.data?.launches?.cursor
19            }
20        }
21    }
22}

Note that we wrap the cursor in an Optional: this is because this parameter can be omitted in the query.

Test scrolling

Click Run. You can now see all SpaceX launches back to their first FalconSat from Kwajalein Atoll!

Next, you'll add a details view that will allow you to book a seat on a launch.

Feedback

Edit on GitHub

Forums