Search code examples
swiftgoogle-cloud-firestoreconcurrency

Is it possible to add a try await method to a viewDidLoad() function and if so how?


I have been using this Google Firebase method below where I load the user data as they open the view controller, however it is unable to work as I get the error "'async' call in a function that does not support concurrency" and "Add 'async' to function 'viewDidLoad()' to make it asynchronous" which only further creates issues. This is code the code below, please tell me how I may be able to get past this (it has to be in viewDidLoad())

    override func viewDidLoad() async {
        super.viewDidLoad()

        
        let db = Firestore.firestore()
        let docRef = db.collection("cities").document("SF")
        
        do {
            // Force the SDK to fetch the document from the cache. Could also specify
            // FirestoreSource.server or FirestoreSource.default.
            let document = try await docRef.getDocument(source: .cache)
            if document.exists {
                let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
                print("Cached document data: \(dataDescription)")
            } else {
                print("Document does not exist in cache")
            }
        } catch {
            print("Error getting document: \(error)")
        }
    }

I have tried varying methods of trying to reduce the issue, such as recreating the save method using a "Task {}" method, however it would cause issues with a QuerySnapshot (The QuerySnapshot is in my personal code and is not in the code above so try not to dwell into that.) I also review the other methods of Googles Firebase implementations and they all feature the "Try await" coding unit, so I am trying to figure a way out for View did load.

I would also like to confirm as the Stack Overflow team constantly pesters me for "Duplicate" questions which have literally 0 correlation to my question, that indeed this question is not a duplicate, was not answered on this platform, is not a repost and is not able to be answered outside of this platform (from my searching at least.)


Solution

  • First, this cannot work:

    override func viewDidLoad() async {
    

    viewDidLoad is not an asynchronous method. You cannot make it an asynchronous method just by adding async. viewDidLoad() must be complete by the time it returns, and it must be synchronous. That is the nature of the method, which not only predates Swift Concurrency, it predates Swift.

    If you would like to create an asynchronous context during viewDidLoad(), you can do that with Task {}, for example:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        let db = Firestore.firestore()
        let docRef = db.collection("cities").document("SF")
        
        Task {
            do {
                ... everything else
        }
    }
    

    That does not promise that the Task will complete before viewDidLoad() returns. (It absolutely will not.) But this will begin the work during viewDidLoad.

    What possibly is causing confusion is that it is impossible, by design, to avoid displaying something while the network call is progressing. Imagine if the network call took minutes to complete, and then returned an error? What would your system display in the meantime, and after the error? You must deal with that situation. You cannot just block loading the view until you have data from the network. It might never come. So instead you must display something, and when the network data shows up, you will update the view.