From 7b98f5216e9d79f837974c28caf9a8137c6efc2f Mon Sep 17 00:00:00 2001 From: Steve Dao Date: Tue, 9 Mar 2021 00:36:49 +0700 Subject: [PATCH 1/5] feat: Added an option to enable/disable first reload --- .../BatchesDataSource/BatchesDataSource.swift | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Sources/CombineDataSources/BatchesDataSource/BatchesDataSource.swift b/Sources/CombineDataSources/BatchesDataSource/BatchesDataSource.swift index 52a039e..cf4c189 100644 --- a/Sources/CombineDataSources/BatchesDataSource/BatchesDataSource.swift +++ b/Sources/CombineDataSources/BatchesDataSource/BatchesDataSource.swift @@ -100,7 +100,7 @@ public struct BatchesDataSource { case data(Data?) } - private init(items: [Element] = [], input: BatchesInput, initial: Token, loadNextCallback: @escaping (Token) -> AnyPublisher) { + private init(items: [Element] = [], input: BatchesInput, initial: Token, isFirstReload: Bool = true, loadNextCallback: @escaping (Token) -> AnyPublisher) { let itemsSubject = CurrentValueSubject<[Element], Never>(items) let token = CurrentValueSubject(initial) @@ -115,10 +115,17 @@ public struct BatchesDataSource { let loadNext = input.loadNext .map { token.value } - - let batchRequest = loadNext + + var batchRequest: AnyPublisher! + if isFirstReload { + batchRequest = loadNext .merge(with: input.reload.prepend(()).map { initial }) .eraseToAnyPublisher() + } else { + batchRequest = loadNext + .merge(with: input.reload.map { initial }) + .eraseToAnyPublisher() + } // TODO: avoid having extra subject when `shareReplay()` is introduced. let batchResponse = PassthroughSubject() @@ -217,8 +224,8 @@ public struct BatchesDataSource { /// - Parameter loadItemsWithToken: a `(Data?) -> (Publisher)` closure that fetches a batch of items and returns the items fetched /// plus a token to use for the next batch. The token can be an alphanumerical id, a URL, or another type of token. /// - Todo: if `withLatestFrom` is introduced, use it instead of grabbing the latest value unsafely. - public init(items: [Element] = [], input: BatchesInput, initialToken: Data?, loadItemsWithToken: @escaping (Data?) -> AnyPublisher) { - self.init(items: items, input: input, initial: Token.data(initialToken), loadNextCallback: { token -> AnyPublisher in + public init(items: [Element] = [], input: BatchesInput, initialToken: Data?, isFirstReload: Bool = true, loadItemsWithToken: @escaping (Data?) -> AnyPublisher) { + self.init(items: items, input: input, initial: Token.data(initialToken), isFirstReload: isFirstReload, loadNextCallback: { token -> AnyPublisher in switch token { case .data(let data): return loadItemsWithToken(data) @@ -233,8 +240,8 @@ public struct BatchesDataSource { /// - Parameter initialPage: the page number to use for the first load of items. /// - Parameter loadPage: a `(Int) -> (Publisher)` closure that fetches a batch of items. /// - Todo: if `withLatestFrom` is introduced, use it instead of grabbing the latest value unsafely. - public init(items: [Element] = [], input: BatchesInput, initialPage: Int = 0, loadPage: @escaping (Int) -> AnyPublisher) { - self.init(items: items, input: input, initial: Token.int(initialPage), loadNextCallback: { page -> AnyPublisher in + public init(items: [Element] = [], input: BatchesInput, initialPage: Int = 0, isFirstReload: Bool = true, loadPage: @escaping (Int) -> AnyPublisher) { + self.init(items: items, input: input, initial: Token.int(initialPage), isFirstReload: isFirstReload, loadNextCallback: { page -> AnyPublisher in switch page { case .int(let page): return loadPage(page) From ee6e2834a7f637d59508d85fd7762e8f0eda81e5 Mon Sep 17 00:00:00 2001 From: Steve Dao Date: Tue, 9 Mar 2021 00:42:05 +0700 Subject: [PATCH 2/5] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 164afc9..4aaa8bb 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,14 @@ let dataSource = BatchesDataSource( items: ["Initial Element"], input: input, initialToken: nil, + isFirstLoad: true, loadItemsWithToken: { token in return MockAPI.requestBatchCustomToken(token) }) ``` +> By default, the data source will trigger an input's reload automatically to load the first batch of items. If you want to trigger manually, set the BatchesDataSource's isFirstLoad property to **false** + `dataSource` is controlled via the two inputs: - `input.reload` (to reload the very first batch) and From 77a44e805aa6aa86e048c4e16863eb176289a140 Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 9 Mar 2021 01:26:34 +0700 Subject: [PATCH 3/5] chore: added unit test for isFirstReload --- .../BatchesDataSourceTests.swift | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Tests/CombineDataSourcesTests/BatchesDataSource/BatchesDataSourceTests.swift b/Tests/CombineDataSourcesTests/BatchesDataSource/BatchesDataSourceTests.swift index 32dc8d0..b24f42a 100644 --- a/Tests/CombineDataSourcesTests/BatchesDataSource/BatchesDataSourceTests.swift +++ b/Tests/CombineDataSourcesTests/BatchesDataSource/BatchesDataSourceTests.swift @@ -283,4 +283,41 @@ final class BatchesDataSourceTests: XCTestCase { wait(for: [controlEvent], timeout: 1) } + + func testReloadManually() { + let testStrings = ["test1", "test2"] + var subscriptions = [AnyCancellable]() + let inputControls = self.inputControls + + let batcher = BatchesDataSource(items: testStrings, input: inputControls.input, isFirstReload: false) { page in + return Future.LoadResult, Error> { promise in + DispatchQueue.main.async { + promise(.success(.items(["test3"]))) + } + }.eraseToAnyPublisher() + } + + let controlEvent = expectation(description: "Wait for control event") + + batcher.output.$items + .dropFirst(1) + .prefix(2) + .collect() + .sink(receiveCompletion: { _ in + controlEvent.fulfill() + }) { values in + XCTAssertEqual([ + testStrings, + testStrings + ["test3"] + ], values) + } + .store(in: &subscriptions) + + DispatchQueue.global().async { + inputControls.reload.send() + inputControls.loadNext.send() + } + + wait(for: [controlEvent], timeout: 1) + } } From 6f56dac3e076149a24db34445bc6af502226c4ca Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 9 Mar 2021 01:31:27 +0700 Subject: [PATCH 4/5] chore: Updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4aaa8bb..76ef02a 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ let dataSource = BatchesDataSource( ``` > By default, the data source will trigger an input's reload automatically to load the first batch of items. If you want to trigger manually, set the BatchesDataSource's isFirstLoad property to **false** +> The **dataSource** variable must be retained to keep all of its output's subscriptions be alive, initialize it inside a local scope (such as viewDidLoad method) cause all of its output's subscriptions are cancelled `dataSource` is controlled via the two inputs: From 255c5ec56e8eba4617397b313a4d96e6bf985b55 Mon Sep 17 00:00:00 2001 From: Steve Dao Date: Tue, 9 Mar 2021 01:34:54 +0700 Subject: [PATCH 5/5] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76ef02a..42db92b 100644 --- a/README.md +++ b/README.md @@ -110,13 +110,14 @@ let dataSource = BatchesDataSource( items: ["Initial Element"], input: input, initialToken: nil, - isFirstLoad: true, + isFirstReload: true, loadItemsWithToken: { token in return MockAPI.requestBatchCustomToken(token) }) ``` -> By default, the data source will trigger an input's reload automatically to load the first batch of items. If you want to trigger manually, set the BatchesDataSource's isFirstLoad property to **false** +> By default, the data source will trigger an input's reload automatically to load the first batch of items. If you want to trigger manually, set the BatchesDataSource's isFirstReload property to **false** +> > The **dataSource** variable must be retained to keep all of its output's subscriptions be alive, initialize it inside a local scope (such as viewDidLoad method) cause all of its output's subscriptions are cancelled `dataSource` is controlled via the two inputs: