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>.

Swift
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.

Swift
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:

Swift
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)
Feedback

Edit on GitHub

Forums