SwiftX
SwiftX

Reputation: 98

How to infer a generic paramater with an async/await function

I have an async/await function to make sure that the data gets passed along first;

func downloadFirebaseData() async -> String {
    let group = DispatchGroup()
    group.enter()    // stop the thread/enter the function
        
    let db = Firestore.firestore()
    withUnsafeThrowingContinuation { continuation in
        db.collection("annotations")
            .getDocuments { (querySnapshot, error) in        
                defer {
                    group.leave()    // << end on any return
                }    
                if let Lng = i.document.get("lng") as? String {
                    DispatchQueue.main.async {
                        annotationLng.append(Lng) //edit the array
                        print("downloadLngServerData ()\(annotationLng)")
                    }
                }
                if let Lat = i.document.get("lat") as? String {
                    DispatchQueue.main.async {
                        annotationLat.append(Lat) //edit the array
                        print("downloadLatServerData ()\(annotationLat)")
                    }
                }
            }        
    }
    group.wait() // clear up the thread now, exit the function
}

And its called under my view with;

.task {
    try await downloadFirebaseData() //error 1 
}
@State annotationLat: [String] = []
@State annotationLng: [String] = []

Inside of firebase database:

annotationLat = ["42.828392","29.18273","97.27352"]
annotationLng = ["42.828392","29.18273","97.27352"]

I have 2 errors;

Invalid conversion from throwing function of type '@Sendable () async throws -> Void' to non-throwing function type '@Sendable () async -> Void'

This was under the .task

My second error:

Generic parameter 'T' could not be inferred

This was under withUnsafeThrowingContinuation

The first error I somewhat get, but even after I modified from my original code, the error still persisted.

For the second error, I know that I might have to define that this is a string somewhere, because I don't think that the app knows that I'm trying to work with a string.

Upvotes: 0

Views: 635

Answers (1)

lorem ipsum
lorem ipsum

Reputation: 29614

This assumes that the Firestore path for the documents is

annotations/{id}

and that each document has variables lat and lng of type String

enter image description here

import Foundation
import FirebaseFirestoreSwift
import FirebaseFirestore
import CoreLocation
//struct to keep the latitude and longitude together, they should not be in separate arrays
struct Annotation: Codable, Identifiable{
    @DocumentID var id: String?
    var lat: String?
    var lng: String?
}
extension Annotation{
    //Safely unwrap the Strings into doubles and then create the coordinate
    var coordinate: CLLocationCoordinate2D? {
        guard let latStr = lat, let lngStr = lng, let latitude = Double(latStr), let longitude = Double(lngStr) else{
            print("Unable to get valid latitude and longitude")
            return nil
        }
        let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        return coordinate
    }
}
struct CustomFirestoreService{
    let store: Firestore = .firestore()
    init(){}
    func getAnnotations() async throws -> [Annotation]{
        let ANNOTATIONS_PATH = "annotations"
        return try await retrieve(path: ANNOTATIONS_PATH)
    }
    ///retrieves all the documents in the collection at the path
    private func retrieve<FC : Codable>(path: String) async throws -> [FC]{
        //Firebase provided async await.
        let querySnapshot = try await store.collection(path).getDocuments()
        return querySnapshot.documents.compactMap { document in
            do{
                return try document.data(as: FC.self)
            }catch{
                print(error)
                return nil
            }
        }
    }
}

Then in your View

import SwiftUI
struct AnnotationsView: View {
    let service: CustomFirestoreService = CustomFirestoreService()
    @State private var annotations: [Annotation] = []
    var body: some View {
        if annotations.isEmpty{
            Text("Hello, World!")
                .task {
                    do{
                        annotations = try await service.getAnnotations()
                        //Do any other work here, this line won't run unless the annotations are populated.
                    }catch{
                        print(error)
                    }
                }
        }else{
            List(annotations){ annotation in
                if let coord = annotation.coordinate{
                    VStack{
                        Text("Latitude = \(coord.latitude)")
                        Text("Longitude = \(coord.longitude)")
                    }
                }else{
                    Text("Invalid Coordinate Value. Check firestore values for document \(annotation.id ?? "no id")")
                }
            }
        }
    }
}

struct AnnotationsView_Previews: PreviewProvider {
    static var previews: some View {
        AnnotationsView()
    }
}

This makes some assumptions but if you paste it into your project you should get some working code.

You don't need this for your code but this is what a conversion from the "old" closures to the new async await would look like.

public func retrieve<FC : Codable>(path: String) async throws -> [FC]{
    typealias MyContinuation = CheckedContinuation<[FC], Error>
    return try await withCheckedThrowingContinuation { (continuation: MyContinuation) in
        store.collection(path)
            .getDocuments() { (querySnapshot, err) in
                if let err = err {
                    //This throws an error
                    continuation.resume(throwing: err)
                } else {
                    let array = querySnapshot?.documents.compactMap { document in
                        try? document.data(as: FC.self)
                    } ?? []
                    //This returns an array
                    continuation.resume(returning: array)
                }
            }
    }
}

If you aren't calling continuation there is no point in returning a continuation of any kind.

Upvotes: 1

Related Questions