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:
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:
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.
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 paging in Compose. In a real project you may use something more advanced, like the Jetpack Paging library.
The whole function should look like this:
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 anOptional
: 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.