First of all, I'm new to rxswift so I guess the answer is obvious however at the moment I can't find solution by myself.
I have two functions:
func downloadAllTasks() -> Observable<[Task]>
func getTaskDetails(taskId: Int64) -> Observable<TaskDetails>
First one is downloading the list of Task objects using network request, second one downloading task details for sepcific task (using it's id)
What I want of achieve is to download all tasks and then for each task I want to download its details and subscribe for the event fired when all tasks details are ready.
So I guess I should subscribe somehow to Observable<[TaskDetails]> but I don't know how to do it.
downloadAllTasks()
.flatMap{
... // flatMap? something else?
}
.subscribe(
onNext: { details in
print("tasks details: \(details.map{$0.name})")
})
.addDisposableTo(disposeBag)
//EDIT
Thanks to Silvan Mosberger answer I'm much closer to the solution. One problem left. Now I have something like this:
downloadAllTasks()
.flatMap{ Observable.from($0) }
.map{ $0.id }
.flatMap{ [unowned self] id in
self.getTaskDetails(taskId: id).catchError{ error in
print("$$$ Error downloading task \(id)")
return .empty()
}
}
.do(onNext: { _ in
print(" $$$ single task details downloaded")
} )
.toArray()
.debug("$$$ task details array debug", trimOutput: false)
.subscribe({ _ in
print("$$$ all tasks downloaded")
})
.addDisposableTo(disposeBag)
The output is
$$$ task details array debug -> subscribed
$$$ single task details downloaded
$$$ single task details downloaded
$$$ single task details downloaded
There are 3 tasks available so as you can se all of them are downloaded properly however for some reason the result of toArray() - (Observable<[TaskDetails]>
) doesn't produce "onNext" once all task details are ready.
// Edit once more
Ok, I'm adding simplified version of functions providing observables, maybe it will help something
func downloadAllTasks() -> Observable<Task> {
return Observable.create { observer in
//... network request to download tasks
//...
for task in tasks {
observer.onNext(task)
}
observer.onCompleted()
return Disposables.create()
}
}
func getTaskDetails(id: Int64) -> Observable< TaskDetails > {
return Observable.create { observer in
//... network request to download task details
//...
observer.onNext(taskDetails)
return Disposables.create()
}
}
With RxSwift you want to use
Observable
s whenever possible, therefore I recommend you to refactor thedownloadAllTasks
method to return anObservable<Task>
. This should be fairly trivial by just looping through the elements instead of emitting the array directly:If this is not possible for whatever reason, there is also an operator for that in RxSwift:
In the following code I will be using the refactored
downloadAllTasks() -> Observable<Task>
method because it's the cleaner approach.You can then
map
your tasks to get their id (assuming yourTask
type has theid: Int64
property) andflatMap
with thedownloadAllTasks
function to get anObservable<TaskDetails>
:Then you can use the
toArray()
operator to gather the whole sequence and emit an event containing all elements in an array:In short, without type annotations and sharing the tasks (so you won't download them only once):
EDIT: Note that this Observable will error when any of the detail downloads encounters an error. I'm not exactly sure what's the best way to prevent this, but this does work:
EDIT2: It's not gonna work if your
getTaskDetails
returns an observable that never completes. Here is a simple reference implementation ofgetTaskDetails
(withString
instead ofTaskDetails
), using JSONPlaceholder: