diff --git a/README.md b/README.md index 164afc9..42db92b 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,16 @@ let dataSource = BatchesDataSource( items: ["Initial Element"], input: input, initialToken: nil, + 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 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: - `input.reload` (to reload the very first batch) and 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) 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) + } }