Skip to content

Commit dbdb35d

Browse files
gh-action-runnergh-action-runner
authored andcommitted
Squashed 'apollo-ios-pagination/' changes from 017a468c..b591b09c
b591b09c [ApolloPagination] Update `PaginationOutput` to operate over `GraphQLResult`s (#428) git-subtree-dir: apollo-ios-pagination git-subtree-split: b591b09cc9891e16d699925be4fb706b37ff2714
1 parent 9d26d8e commit dbdb35d

8 files changed

+193
-498
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let package = Package(
88
.iOS(.v13),
99
.macOS(.v10_15),
1010
.tvOS(.v13),
11-
.watchOS(.v6)
11+
.watchOS(.v6),
1212
],
1313
products: [
1414
.library(name: "ApolloPagination", targets: ["ApolloPagination"]),

Sources/ApolloPagination/AsyncGraphQLQueryPager.swift

Lines changed: 30 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Foundation
66
/// Type-erases a query pager, transforming data from a generic type to a specific type, often a view model or array of view models.
77
public class AsyncGraphQLQueryPager<Model>: Publisher {
88
public typealias Failure = Never
9-
public typealias Output = Result<(Model, UpdateSource), any Error>
9+
public typealias Output = Result<Model, any Error>
1010
let _subject: CurrentValueSubject<Output?, Never> = .init(nil)
1111
var publisher: AnyPublisher<Output, Never> { _subject.compactMap({ $0 }).eraseToAnyPublisher() }
1212
@Atomic public var cancellables: Set<AnyCancellable> = []
@@ -17,7 +17,7 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
1717

1818
init<Pager: AsyncGraphQLQueryPagerCoordinator<InitialQuery, PaginatedQuery>, InitialQuery, PaginatedQuery>(
1919
pager: Pager,
20-
transform: @escaping ([PaginatedQuery.Data], InitialQuery.Data, [PaginatedQuery.Data]) throws -> Model
20+
transform: @escaping (PaginationOutput<InitialQuery, PaginatedQuery>) throws -> Model
2121
) {
2222
self.pager = pager
2323
Task {
@@ -26,10 +26,10 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
2626
let returnValue: Output
2727

2828
switch result {
29-
case let .success((output, source)):
29+
case let .success(output):
3030
do {
31-
let transformedModels = try transform(output.previousPages, output.initialPage, output.nextPages)
32-
returnValue = .success((transformedModels, source))
31+
let transformedModels = try transform(output)
32+
returnValue = .success(transformedModels)
3333
} catch {
3434
returnValue = .failure(error)
3535
}
@@ -50,89 +50,19 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
5050
Task {
5151
let cancellable = await pager.subscribe { [weak self] result in
5252
guard let self else { return }
53-
let returnValue: Output
54-
55-
switch result {
56-
case let .success((output, source)):
57-
returnValue = .success((output, source))
58-
case let .failure(error):
59-
returnValue = .failure(error)
60-
}
61-
62-
_subject.send(returnValue)
53+
_subject.send(result)
6354
}
6455
_ = $cancellables.mutate { $0.insert(cancellable) }
6556
}
6657
}
6758

68-
convenience init<
69-
Pager: AsyncGraphQLQueryPagerCoordinator<InitialQuery, PaginatedQuery>,
70-
InitialQuery,
71-
PaginatedQuery,
72-
Element
73-
>(
74-
pager: Pager,
75-
initialTransform: @escaping (InitialQuery.Data) throws -> Model,
76-
pageTransform: @escaping (PaginatedQuery.Data) throws -> Model
77-
) where Model: RangeReplaceableCollection, Model.Element == Element {
78-
self.init(
79-
pager: pager,
80-
transform: { previousData, initialData, nextData in
81-
let previous = try previousData.flatMap { try pageTransform($0) }
82-
let initial = try initialTransform(initialData)
83-
let next = try nextData.flatMap { try pageTransform($0) }
84-
return previous + initial + next
85-
}
86-
)
87-
}
88-
89-
public convenience init<
90-
P: PaginationInfo,
91-
InitialQuery: GraphQLQuery,
92-
PaginatedQuery: GraphQLQuery,
93-
Element
94-
>(
95-
client: any ApolloClientProtocol,
96-
initialQuery: InitialQuery,
97-
watcherDispatchQueue: DispatchQueue = .main,
98-
extractPageInfo: @escaping (PageExtractionData<InitialQuery, PaginatedQuery, Model?>) -> P,
99-
pageResolver: ((P, PaginationDirection) -> PaginatedQuery?)?,
100-
initialTransform: @escaping (InitialQuery.Data) throws -> Model,
101-
pageTransform: @escaping (PaginatedQuery.Data) throws -> Model
102-
) where Model: RangeReplaceableCollection, Model.Element == Element {
103-
let pager = AsyncGraphQLQueryPagerCoordinator(
104-
client: client,
105-
initialQuery: initialQuery,
106-
watcherDispatchQueue: watcherDispatchQueue,
107-
extractPageInfo: { data in
108-
switch data {
109-
case .initial(let data, let output):
110-
return extractPageInfo(.initial(data, convertOutput(result: output)))
111-
case .paginated(let data, let output):
112-
return extractPageInfo(.paginated(data, convertOutput(result: output)))
113-
}
114-
},
115-
pageResolver: pageResolver
116-
)
117-
self.init(
118-
pager: pager,
119-
initialTransform: initialTransform,
120-
pageTransform: pageTransform
121-
)
122-
123-
func convertOutput(result: PaginationOutput<InitialQuery, PaginatedQuery>?) -> Model? {
124-
guard let result else { return nil }
125-
126-
let transform: ([PaginatedQuery.Data], InitialQuery.Data, [PaginatedQuery.Data]) throws -> Model = { previousData, initialData, nextData in
127-
let previous = try previousData.flatMap { try pageTransform($0) }
128-
let initial = try initialTransform(initialData)
129-
let next = try nextData.flatMap { try pageTransform($0) }
130-
return previous + initial + next
131-
}
132-
return try? transform(result.previousPages, result.initialPage, result.nextPages)
133-
}
134-
}
135-
59+
/// Initialize an `AsyncGraphQLQueryPager` that outputs a `PaginationOutput<InitialQuery, PaginatedQuery>`.
60+
/// - Parameters:
61+
/// - client: Apollo client type
62+
/// - initialQuery: The query to call for the first page of pagination. May be a separate type of query than the pagination query.
63+
/// - watcherDispatchQueue: The queue that the underlying `GraphQLQueryWatcher`s respond on. Defaults to `main`.
64+
/// - extractPageInfo: A user-input closure that instructs the pager on how to extract `P`, a `PaginationInfo` type, from the `Data` of either the `InitialQuery` or `PaginatedQuery`.
65+
/// - pageResolver: A user-input closure that instructs the pager on how to create a new `PaginatedQuery` given a `PaginationInfo` and a `PaginationDirection`.
13666
public convenience init<
13767
P: PaginationInfo,
13868
InitialQuery: GraphQLQuery,
@@ -151,11 +81,17 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
15181
extractPageInfo: extractPageInfo,
15282
pageResolver: pageResolver
15383
)
154-
self.init(
155-
pager: pager
156-
)
84+
self.init(pager: pager)
15785
}
15886

87+
/// Initialize an `AsyncGraphQLQueryPager` that outputs a user-defined `Model`, the result of the `transform` argument.
88+
/// - Parameters:
89+
/// - client: Apollo client type
90+
/// - initialQuery: The query to call for the first page of pagination. May be a separate type of query than the pagination query.
91+
/// - watcherDispatchQueue: The queue that the underlying `GraphQLQueryWatcher`s respond on. Defaults to `main`.
92+
/// - extractPageInfo: A user-input closure that instructs the pager on how to extract `P`, a `PaginationInfo` type, from the `Data` of either the `InitialQuery` or `PaginatedQuery`.
93+
/// - pageResolver: A user-input closure that instructs the pager on how to create a new `PaginatedQuery` given a `PaginationInfo` and a `PaginationDirection`.
94+
/// - transform: Transforms the `PaginationOutput` into a `Model` type.
15995
public convenience init<
16096
P: PaginationInfo,
16197
InitialQuery: GraphQLQuery,
@@ -164,38 +100,23 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
164100
client: any ApolloClientProtocol,
165101
initialQuery: InitialQuery,
166102
watcherDispatchQueue: DispatchQueue = .main,
167-
extractPageInfo: @escaping (PageExtractionData<InitialQuery, PaginatedQuery, Model?>) -> P,
103+
extractPageInfo: @escaping (PageExtractionData<InitialQuery, PaginatedQuery, PaginationOutput<InitialQuery, PaginatedQuery>?>) -> P,
168104
pageResolver: ((P, PaginationDirection) -> PaginatedQuery?)?,
169-
transform: @escaping ([PaginatedQuery.Data], InitialQuery.Data, [PaginatedQuery.Data]) throws -> Model
105+
transform: @escaping (PaginationOutput<InitialQuery, PaginatedQuery>) throws -> Model
170106
) {
171107
let pager = AsyncGraphQLQueryPagerCoordinator(
172108
client: client,
173109
initialQuery: initialQuery,
174110
watcherDispatchQueue: watcherDispatchQueue,
175-
extractPageInfo: { data in
176-
switch data {
177-
case .initial(let data, let output):
178-
return extractPageInfo(.initial(data, convertOutput(result: output)))
179-
case .paginated(let data, let output):
180-
return extractPageInfo(.paginated(data, convertOutput(result: output)))
181-
}
182-
},
111+
extractPageInfo: extractPageInfo,
183112
pageResolver: pageResolver
184113
)
185-
self.init(
186-
pager: pager,
187-
transform: transform
188-
)
189-
190-
func convertOutput(result: PaginationOutput<InitialQuery, PaginatedQuery>?) -> Model? {
191-
guard let result else { return nil }
192-
return try? transform(result.previousPages, result.initialPage, result.nextPages)
193-
}
114+
self.init(pager: pager, transform: transform)
194115
}
195116

196-
197117
/// Subscribe to the results of the pager, with the management of the subscriber being stored internally to the `AnyGraphQLQueryPager`.
198118
/// - Parameter completion: The closure to trigger when new values come in.
119+
@available(*, deprecated, message: "Will be removed in a future version of ApolloPagination. Use the `Combine` publishers instead. If you need to dispatch to the main thread, make sure to use a `.receive(on: RunLoop.main)` as part of your `Combine` operation.")
199120
public func subscribe(completion: @MainActor @escaping (Output) -> Void) {
200121
let cancellable = publisher.sink { result in
201122
Task { await completion(result) }
@@ -221,7 +142,7 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
221142
try await pager.loadPrevious(cachePolicy: cachePolicy)
222143
}
223144

224-
/// Loads all pages.
145+
/// Loads all pages. Does not output a value until all pages have loaded.
225146
/// - Parameters:
226147
/// - fetchFromInitialPage: Pass true to begin loading from the initial page; otherwise pass false. Defaults to `true`. **NOTE**: Loading all pages with this value set to `false` requires that the initial page has already been loaded previously.
227148
public func loadAll(
@@ -243,30 +164,12 @@ public class AsyncGraphQLQueryPager<Model>: Publisher {
243164

244165
/// Resets pagination state and cancels in-flight updates from the pager.
245166
public func reset() async {
246-
await pager.reset()
167+
await pager.reset()
247168
}
248169

249170
public func receive<S>(
250171
subscriber: S
251-
) where S: Subscriber, Never == S.Failure, Result<(Model, UpdateSource), any Error> == S.Input {
172+
) where S: Subscriber, Never == S.Failure, Result<Model, any Error> == S.Input {
252173
publisher.subscribe(subscriber)
253174
}
254175
}
255-
256-
extension AsyncGraphQLQueryPager: Equatable where Model: Equatable {
257-
public static func == (lhs: AsyncGraphQLQueryPager<Model>, rhs: AsyncGraphQLQueryPager<Model>) -> Bool {
258-
let left = lhs._subject.value
259-
let right = rhs._subject.value
260-
261-
switch (left, right) {
262-
case (.success((let leftValue, let leftSource)), .success((let rightValue, let rightSource))):
263-
return leftValue == rightValue && leftSource == rightSource
264-
case (.failure(let leftError), .failure(let rightError)):
265-
return leftError.localizedDescription == rightError.localizedDescription
266-
case (.none, .none):
267-
return true
268-
default:
269-
return false
270-
}
271-
}
272-
}

0 commit comments

Comments
 (0)