Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable/disable first reload of BatchesDataSource #13

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,16 @@ let dataSource = BatchesDataSource<String>(
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public struct BatchesDataSource<Element> {
case data(Data?)
}

private init(items: [Element] = [], input: BatchesInput, initial: Token, loadNextCallback: @escaping (Token) -> AnyPublisher<LoadResult, Error>) {
private init(items: [Element] = [], input: BatchesInput, initial: Token, isFirstReload: Bool = true, loadNextCallback: @escaping (Token) -> AnyPublisher<LoadResult, Error>) {
let itemsSubject = CurrentValueSubject<[Element], Never>(items)
let token = CurrentValueSubject<Token, Never>(initial)

Expand All @@ -115,10 +115,17 @@ public struct BatchesDataSource<Element> {

let loadNext = input.loadNext
.map { token.value }

let batchRequest = loadNext

var batchRequest: AnyPublisher<Token, Never>!
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<ResponseResult, Never>()
Expand Down Expand Up @@ -217,8 +224,8 @@ public struct BatchesDataSource<Element> {
/// - Parameter loadItemsWithToken: a `(Data?) -> (Publisher<LoadResult, Error>)` 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<LoadResult, Error>) {
self.init(items: items, input: input, initial: Token.data(initialToken), loadNextCallback: { token -> AnyPublisher<LoadResult, Error> in
public init(items: [Element] = [], input: BatchesInput, initialToken: Data?, isFirstReload: Bool = true, loadItemsWithToken: @escaping (Data?) -> AnyPublisher<LoadResult, Error>) {
self.init(items: items, input: input, initial: Token.data(initialToken), isFirstReload: isFirstReload, loadNextCallback: { token -> AnyPublisher<LoadResult, Error> in
switch token {
case .data(let data):
return loadItemsWithToken(data)
Expand All @@ -233,8 +240,8 @@ public struct BatchesDataSource<Element> {
/// - Parameter initialPage: the page number to use for the first load of items.
/// - Parameter loadPage: a `(Int) -> (Publisher<LoadResult, Error>)` 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<LoadResult, Error>) {
self.init(items: items, input: input, initial: Token.int(initialPage), loadNextCallback: { page -> AnyPublisher<LoadResult, Error> in
public init(items: [Element] = [], input: BatchesInput, initialPage: Int = 0, isFirstReload: Bool = true, loadPage: @escaping (Int) -> AnyPublisher<LoadResult, Error>) {
self.init(items: items, input: input, initial: Token.int(initialPage), isFirstReload: isFirstReload, loadNextCallback: { page -> AnyPublisher<LoadResult, Error> in
switch page {
case .int(let page):
return loadPage(page)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>(items: testStrings, input: inputControls.input, isFirstReload: false) { page in
return Future<BatchesDataSource<String>.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)
}
}