I have two network requests that I would like to combine the results in one array of Object.
enum ResultType<T> {
case success(T)
case failure(Error)
}
static func requestPlaceSearch(query: String) -> Observable<ResultType<SearchResult>> {
let urlString = "https://maps.googleapis.com/maps/api/place/autocomplete/json"
let params = ["input": query, "types": "geocode",
"key": "key"]
return placeRequest(url: urlString, params: params)
}
static func requestPlace(with placeId: String) -> Observable<ResultType<Place>> {
let params = ["placeid": placeId, "key": "key"]
let urlString = "https://maps.googleapis.com/maps/api/place/details/json"
return placeRequest(url: urlString, params: params)
}
requestPlaceSearch
returns and observable of SearchResult
I want to loop through SearchResult.predictions
, get the id of each prediction make a request to requestPlace
get the Place
an append to array of Places
I want my final array to be
let places = Observable<[Place]>
or
let places = [Place]()
I'm going to give you the answer, but I'm going to draw it out by explaining how I came up with the answer. Hopefully, you can emulate the process and be able to come up with the answer yourself in the next attempt.
Start with:
let foo = requestPlaceSearch(query: "")
And look at foo
's type which is: Observable<ResultType<SearchResult>>
. Okay from there we need to extract the search result from the result type. So...
let foo = requestPlaceSearch(query: "")
.map { (result: ResultType<SearchResult>) -> SearchResult? in
if case let .success(search) = result {
return search
} else {
return nil
}
}
Now what is foo
's type? Observable<SearchResult?>
. So lets filter out the optionals.
let foo = requestPlaceSearch(query: "")
.map { (result: ResultType<SearchResult>) -> SearchResult? in
if case let .success(search) = result {
return search
} else {
return nil
}
}
.filter { $0 != nil }.map { $0! }
Now foo
is of type: Observable<SearchResult>
So we can pull out the predictions
.
let foo = requestPlaceSearch(query: "")
.map { (result: ResultType<SearchResult>) -> SearchResult? in
if case let .success(search) = result {
return search
} else {
return nil
}
}
.filter { $0 != nil }.map { $0! }
.map { $0.predictions }
We are going to need that function to extract the success object again so let's move that into a separate function:
func extractSuccess<T>(_ result: ResultType<T>) -> T? {
if case let .success(search) = result {
return search
} else {
return nil
}
}
I'm assuming that predictions
are just strings. You might have to extract the string IDs from it. So now foo is of type Observable<[String]>
. Now that we have the ids we can call the other endpoint.
let foo = requestPlaceSearch(query: "")
.map(extractSuccess)
.filter { $0 != nil }.map { $0! }
.map { $0.predictions }
.map { $0.map(requestPlace) }
Now foo
is of type Observable<[Observable<ResultType<Place>>]>
. Next let's turn the [Observable] into a Observable<[T]>.
let foo = requestPlaceSearch(query: "")
.map(extractSuccess)
.filter { $0 != nil }.map { $0! }
.map { $0.predictions }
.map { Observable.combineLatest($0.map(requestPlace)) }
Which makes foo a Observable<Observable<[ResultType<Place>]>>
. An Observable> can be flattened out pretty easy.
let foo = requestPlaceSearch(query: "")
.map(extractSuccess)
.filter { $0 != nil }.map { $0! }
.map { $0.predictions }
.flatMap { Observable.combineLatest($0.map(requestPlace)) }
That turns foo into an Observable<[ResultType<Place>]>
let foo = requestPlaceSearch(query: "")
.map(extractSuccess)
.filter { $0 != nil }.map { $0! }
.map { $0.predictions }
.flatMap { Observable.combineLatest($0.map {
requestPlace(with: $0)
.map(extractSuccess)
.filter { $0 != nil }.map { $0! }
})
}
The part I added in the above is the same as the last one that returns a result type. Also, at this point foo
is of type Observable<[Place]>
and we're done.
Note that this code doesn't deal with any of the failure results. I'll leave that as an exercise for the reader. :-)