I am trying to come up with a Combine pipeline that does the following
- Make an API call with certain pageIndex and send the results to subscriber
- Increment the page Index and repeat the above until backend says there is nothing more to fetch
Here is what I have done so far
struct APIResponse {
var hasNextPage: Bool = false
var nextPageIndex: Int = -1
}
class Tester {
func getAllDetails() -> AnyPublisher<APIResponse, Never> {
// Subject holding the page index to be fetched
let subject = CurrentValueSubject<Int, Never>(0)
return subject.flatMap ({ index in
return self.getDetails(index: index)
})
.handleEvents(receiveOutput: { response in
if response.hasNextPage {
subject.send(response.nextPageIndex)
} else {
subject.send(completion: .finished)
}
})
// Ignore the call, Just did it please the compiler
.replaceError(with: APIResponse())
.eraseToAnyPublisher()
}
func getDetails(index: Int) -> AnyPublisher<APIResponse,MockError> {
Future { promise in
// Mocking API Response here
if index < 5 {
promise(.success(APIResponse(hasNextPage: true, nextPageIndex: index 1)))
} else if index == 5 {
promise(.success(APIResponse(hasNextPage: false)))
} else {
promise(.failure(MockError()))
}
}
.eraseToAnyPublisher()
}
}
let tester = Tester()
tester.getAllDetails()
.sink { _ in
print("completed")
} receiveValue: { numbers in
print(numbers.debugDescription)
}
The pipeline is working but it delivering all the results to subscriber at the end and not as they arrive. How do I change this to let subscriber receive intermediate values
CodePudding user response:
There isn't anything wrong with your pipeline. Your problem is that your mock API call isn't realistic; A real API call will be asynchronous. Once you introduce asynchronous behaviour into your mock, you will get the result you expect:
func getDetails(index: Int) -> AnyPublisher<APIResponse,MockError> {
Future { promise in
DispatchQueue.global(qos:.utility).asyncAfter(deadline: .now() 0.5) {
// Mocking API Response here
if index < 5 {
promise(.success(APIResponse(hasNextPage: true, nextPageIndex: index 1)))
} else if index == 5 {
promise(.success(APIResponse(hasNextPage: false)))
} else {
promise(.failure(MockError()))
}
}
}
.eraseToAnyPublisher()
}
APIResponse(hasNextPage: true, nextPageIndex: 1)
APIResponse(hasNextPage: true, nextPageIndex: 2)
APIResponse(hasNextPage: true, nextPageIndex: 3)
APIResponse(hasNextPage: true, nextPageIndex: 4)
APIResponse(hasNextPage: true, nextPageIndex: 5)
APIResponse(hasNextPage: false, nextPageIndex: -1)
completed
You will also need to ensure that your subscriber is retained:
var cancellables = Set<AnyCancellable>()
let tester = Tester()
tester.getAllDetails()
.sink { _ in
print("completed")
} receiveValue: { numbers in
print(numbers)
}.store(in: &cancellables)