Using custom response models
The GraphQLQueryPager
supports not only returning collections of a query's Data
type, but also custom response types. The custom response types can either be a collection or a single object. This is useful when you want to transform the raw response into custom models for consumption in your application.
Transforming existing responses types
The GraphQLQueryPager
class conforms to the Publisher
protocol, and provides a map
method that allows you to transform the raw response into a custom response type. When used this way, the GraphQLQueryPager
will operate over generated Data
types, but allow each Data
type to be transformed into a custom response type. This is especially useful when you want to reuse a single pager in multiple places, transforming the raw data into view models.
In this example, we take a GraphQLQueryPager
that returns a Result<PaginationOutput<MyQuery.Data, MyQuery.Data>>
, and transform the raw response into a custom response type of Result<[Person], Error>
.
1// This `GraphQLQueryPager` will return a `Result` of `PaginationOutput<MyQuery.Data, MyQuery.Data>`
2let initialQuery = MyQuery(first: 10, after: nil)
3let pager = GraphQLQueryPager(
4 client: client,
5 initialQuery: initialQuery,
6 extractPageInfo: { data in
7 CursorBasedPagination.Forward(
8 hasNext: data.values.pageInfo.hasNextPage ?? false,
9 endCursor: data.values.pageInfo.endCursor
10 )
11 },
12 pageResolver: { page, paginationDirection in
13 switch paginationDirection {
14 case .next:
15 return MyQuery(first: 10, after: page.endCursor ?? .none)
16 case .previous:
17 return nil
18 }
19 }
20)
21
22// Elsewhere in the application
23let cancellable = pager.map { result in
24 switch result {
25 case .success((let paginationOutput, let source)):
26 // We may or may not care about the source, but we can use it to transform the response or pass it through as needed.
27 // In this example, we ignore the source and transform the response into a custom response type.
28 let pages = paginationOutput.previousPages + [paginationOutput.initialPage] + paginationOutput.nextPages
29 let people = pages.flatMap { page in
30 data.values.nodes.map { Person(id: $0.id, name: $0.name) }
31 }
32 return Result<[Person], Error>.success(people)
33 case .failure(let error):
34 return .failure(error)
35 }
36}.sink { result in
37 // NOTE: This is not guaranteed to run on the main thread. Dispatch to the main thread if necessary.
38 switch result {
39 case .success(let people):
40 // Handle the people
41 case .failure(let error):
42 // Handle the error
43 }
44}
Creating GraphQLQueryPager
that returns custom response types
The GraphQLQueryPager
class can also be initialized with a custom type in mind. When used this way, the GraphQLQueryPager
will operate over the custom response type, and not the raw Data
type. This is useful when you want to create a pager that returns a custom response type, and you don't want to transform the raw response into a custom response type.
We provide a convenience method for transforming collection types in this manner.
In this example, we create a GraphQLQueryPager
that transform the raw response into a custom response type of Result<([Person], UpdateSource), Error>
. The transform
closure will be called on the data from each paginated query and the resulting arrays will be concatenated together.
1let initialQuery = MyQuery(first: 10, after: nil)
2let pager = GraphQLQueryPager(
3 client: client,
4 initialQuery: initialQuery,
5 extractPageInfo: { data in
6 CursorBasedPagination.Forward(
7 hasNext: data.values.pageInfo.hasNextPage ?? false,
8 endCursor: data.values.pageInfo.endCursor
9 )
10 },
11 pageResolver: { page, paginationDirection in
12 // As we only want to support forward pagination, we can return `nil` for reverse pagination
13 switch paginationDirection {
14 case .next:
15 return MyQuery(first: 10, after: page.endCursor ?? .none)
16 case .previous:
17 return nil
18 }
19 },
20 transform: { data in
21 data.values.nodes.compactMap { node in
22 Person(id: node.id, name: node.name)
23 }
24 }
25)
Alternatively, you can also return a single model type, such as a Result<(MyResponseModel, UpdateSource), Error>
. To do this, we use a transform
closure with three parameters, previousPages, initialPage, nextPages
:
1let initialQuery = MyQuery(first: 10, after: nil)
2let pager = GraphQLQueryPager(
3 client: client,
4 initialQuery: initialQuery,
5 extractPageInfo: { data in
6 CursorBasedPagination.Forward(
7 hasNext: data.values.pageInfo.hasNextPage ?? false,
8 endCursor: data.values.pageInfo.endCursor
9 )
10 },
11 pageResolver: { page, paginationDirection in
12 // As we only want to support forward pagination, we can return `nil` for reverse pagination
13 switch paginationDirection {
14 case .next:
15 return MyQuery(first: 10, after: page.endCursor ?? .none)
16 case .previous:
17 return nil
18 }
19 },
20 transform: { previousPages, initialPage, nextPages in
21 let allPages = previousPages + [initialPage] + nextPages
22 let people = allPages.flatMap { page in
23 data.values.nodes.map { Person(id: $0.id, name: $0.name) }
24 }
25 return MyResponseModel(people: people)
26 }
27)