Home > Mobile >  Async/Await function not not loading data
Async/Await function not not loading data

Time:05-22

I have the following reproduced example in Swift/SwiftUI. I'm simply trying to pull 2 "ratings" from firebase, and then display them once they are loaded. I have 2 possible methods of loading the ratings below.

Method 1: Wait for the data to load, and display it once it's loaded. However, my issue is that this code does not load the data, and my list of ratings remains empty. The database throws no errors, though.

Method 2: Instead of waiting for the data to load, a timer is used to simply wait 2 seconds and hope that the data is loaded by then before displaying it.

Method 1 is what I'm trying to make work. Method 2 works, however, so I know that it isn't an issue with the database or my data, but most likely is an issue with my async/await usage.

Below is my reproduced code. It looks like a lot but it's nothing crazy, sorry!

struct test: View {
    
    @State var ratings: [Double] = []
    @State var ratingsAreLoading = true
    
    func getRatings() async {
                
        let db = Firestore.firestore()                                     //database object
        
        await db.collection("users").document("user 1).getDocument { (doc, error) in
            
            if let doc = doc {                                             //just gets some data
                ratings.append(doc["rating1"] as? Double ?? 1.0)
                ratings.append(doc["rating2"] as? Double ?? 1.0)
            }
        }
    }
    
    var body: some View {
        VStack {
            if ratingsAreLoading {
                Text("LOADING...")
            } else {
                Text(String(ratings[0]))          //displays a rating
            }
        }
        //IMPLEMENTATION HERE
    }
}

Method 1: Doesn't seem to populate "ratings" list

.task {
    await getRatings()                      //gets ratings
    await MainActor.run {
        ratingsAreLoading = false           //displays the ratings
    }
}

Method 2: Works

.onAppear {
    getRatings()                                                 //gets ratings
    DispatchQueue.main.asyncAfter(deadline: .now()   2) {
        ratingsAreLoading = false                                //displays ratings after 2 seconds
    }
}

What am I doing wrong with the async/await method? How can I get it to load my data in time or at all?

CodePudding user response:

You have to convert the completion handler to a Continuation that is compatible with async await

It would look something like this

///Retrieves a single document with the provided id at the collection's path
public func retrieve<FC : FirestoreCodable>(path: String, id: String) async throws -> FC{
    typealias MyContinuation = CheckedContinuation<FC, Error>
    return try await withCheckedThrowingContinuation { (continuation: MyContinuation) in
        let docRef = store.collection(path).document(id)
        docRef.getDocument(as: FC.self){
            result in
            switch result{
            case .success(let object):
                continuation.resume(returning: object)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

To complement the code above I put a property at class/struct level

private let store : Firestore

That way you can add any setup code in the init such as isPersistenceEnabled

FirestoreCodable is just a protocol to help with generics

public protocol FirestoreCodable: Decodable, Encodable{
    /// Wrap with `@DocumentID` from `import FirebaseFirestoreSwift`
    var id: String? { get set }
}

Your struct would look something like

struct User: FirestoreCodable{
    @DocumentID var id: String?
    var rating1: Double
    var rating2: Double
}

You would use it something like

let user: User = try await retrieve(path: "users", id: "user 1")

You can find more about async/await I the WWDC21 video https://developer.apple.com/wwdc21/10132

  • Related