I'm training myself on using ReactiveSwift for networking and a good use case for this seemed to fetch photos for a location from the Google Places API for iOS.
The flow is as follow:
- Get a list of
GMSPlacePhotoMetadata
from a google place ID - For each metadata, fetch a picture
- Concatenates all the pictures as an array
I wrote code doing this workflow in the best ReactiveSwift way I could think of (cf. code below) but when I call my service, although all the API calls to the Google Places API are made, I do not get into the observing part. I feel like I'm missing something really basic from the framework and that I lost some observers along the way, but I can't put my finger where the problem is. Any help will be more than welcome.
My Service Code
import Foundation
import ReactiveSwift
import GooglePlaces
struct GooglePlacesPhotoService {
func findPlacePictures(googlePlaceID: String) -> SignalProducer<[UIImage], DataStoreError> {
return findPlacePicturesMetadata(googlePlaceID: googlePlaceID)
.map { (metadata) -> SignalProducer<UIImage, DataStoreError> in
debugPrint("Mapping metadata to SignalProducer for metadata: ", metadata)
return self.findPlacePicture(metadata: metadata)
} // After mapping, we have a SignalProducer of SignalProducer<UIImage>
.flatten(.merge) // After flatening, we get a single SignalProducer<UIImage>
.reduce([], { (imageArray: [UIImage], newImage: UIImage) -> [UIImage] in
debugPrint("Merging another picture")
return imageArray + [newImage]
}) // Now we have an array of UIImage
}
private func findPlacePicturesMetadata(googlePlaceID: String) -> SignalProducer<GMSPlacePhotoMetadata, DataStoreError> {
return SignalProducer<GMSPlacePhotoMetadata, DataStoreError> { observer, disposable in
GMSPlacesClient.shared().lookUpPhotos(forPlaceID: googlePlaceID) { photos, error in
guard error == nil else { return observer.send(error: .externalError(error!)) }
guard let photos = photos else { return }
photos.results.forEach { metadata in
debugPrint("Sending metadata value: ", metadata)
observer.send(value: metadata)
}
}
}
}
private func findPlacePicture(metadata: GMSPlacePhotoMetadata) -> SignalProducer<UIImage, DataStoreError> {
return SignalProducer<UIImage, DataStoreError> { observer, disposable in
let screenSize = UIScreen.main.bounds.size
let screenScale = UIScreen.main.scale
let myCallback: GMSPlacePhotoImageResultCallback = { image, error in
guard error == nil else {
print("ERROR: couln't load picture for metadata \(metadata)")
observer.send(error: .externalError(error!))
return
}
guard let image = image else {
print("ERROR: empty image returned")
observer.send(error: .unknownExternalError)
return
}
debugPrint("Got 1 picture from metadata: ", metadata)
observer.send(value: image)
}
GMSPlacesClient.shared().loadPlacePhoto(metadata,
constrainedTo: screenSize,
scale: screenScale,
callback: myCallback)
}
}
}
My Observing Code
googlePlaceIDProperty.signal
.filter { $0.isPresent }
.flatMap(.latest) { googlePlaceID in
return GooglePlacesPhotoService().findPlacePictures(googlePlaceID: googlePlaceID!)
}.observe { event in
debugPrint("Signal event!") // I NEVER GET THERE
switch event {
case let .value(pictures):
// Do stuff
case let .failed(error):
// Do stuff
default:
break
}
}
My logs
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645f4d0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645f4d0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645b1b0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645b1b0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645b0f0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645b0f0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x600006459950>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x600006459950>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000644e730>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000644e730>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x60000645ef30>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x60000645ef30>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x6000066420a0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x6000066420a0>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x600006448d60>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x600006448d60>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x600006642130>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x600006642130>
"Sending metadata value: " <GMSPlacePhotoMetadata: 0x6000066421f0>
"Mapping metadata to SignalProducer for metadata: " <GMSPlacePhotoMetadata: 0x6000066421f0>
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645f4d0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645b1b0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645b0f0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x600006459950>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000644e730>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x60000645ef30>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x6000066420a0>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x600006448d60>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x600006642130>
"Merging another picture"
"Got 1 picture from metadata: " <GMSPlacePhotoMetadata: 0x6000066421f0>
"Merging another picture"