Firebaser here. First, please know most (!) Firebase APIs are asynchronous, and require you to use completion handlers to receive the results. This will become easier with the general availability of async/await, which will enable you to write straight-line code. I recorded a video about this a while ago - check it out to get an idea of how you will be able to use Firebase with Swift 5.5.
There are two ways to solve this: using completion handlers or using async/await. I will describe both below, but please note async/await is only available in Swift 5.5 and requires iOS 15, so you might want to opt for completion handlers if you're working on an app that you want to ship to users that use iOS 14.x and below.
Using completion handlers
This is the current way of handling results from Firebase APIs.
Here is an updated version of your code, making use of completion handlers:
func getPledgesInProgress(pledgePicked: Pledge, completionHandler: ([Pledges]) -> Void) {
let db = Firestore.firestore()
var pledgesToReturn = [Pledge]() //INITIALISED AS EMPTY
db.collection("Pledges")
.getDocuments { (snapshot, error) in
guard let snapshot = snapshot, error == nil else {
//handle error
return
}
snapshot.documents.forEach({ (documentSnapshot) in
let documentData = documentSnapshot.data()
pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))
})
// call the completion handler and pass the result array
completionHandler(pledgesToReturn]
}
}
And here is the view:
struct PledgesInProgress: View {
@State var pledgesInProgress = [Pledge]()
var pledgePicked: Pledge?
var body: some View {
VStack {
// LOTS OF CODE HERE WHICH ISN'T RELEVANT
}
.onAppear {
getPledgesInProgress(pledgePicked: pledgePicked) { pledges in
self.pledgesPicked = pledges
}
}
}
}
Using async/await (Swift 5.5)
Your original code it pretty close to the code for an async/await implementation. The main things to keep in mind are:
- mark all asynchronous functions as
async
- call all asynchronous functions using
await
- (if you're using view models) mark your view model as
@MainActor
- to call async code from within SwiftUI, you can either use the
task
view modifier to execute your code when the view appears. Or, if your want to call from a button handler or another synchronous context, wrap the call inside async { await callYourAsyncFunction() }
.
To learn more about this, check out my article Getting Started with async/await in SwiftUI (video coming soon). I've got an article about async/await and Firestore in the pipeline - once it goes live, I will update this answer.
func getPledgesInProgress(pledgePicked: Pledge) async -> [Pledges] {
let db = Firestore.firestore()
var pledgesToReturn = [Pledge]() //INITIALISED AS EMPTY
let snapshot = try await db.collection("Pledges").getDocuments()
snapshot.documents.forEach { documentSnapshot in
let documentData = documentSnapshot.data()
pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))
}
// async/await allows you to return the result just like in a normal function:
return pledgesToReturn
}
}
And here is the view:
struct PledgesInProgress: View {
@State var pledgesInProgress = [Pledge]()
var pledgePicked: Pledge?
var body: some View {
VStack {
// LOTS OF CODE HERE WHICH ISN'T RELEVANT
}
.task {
self.pledgesPicked = await getPledgesInProgress(pledgePicked: pledgePicked)
}
}
}
A few general remarks
There are a couple of things to make your code more resilient:
- Consider using Codable to map your documents. See Mapping Firestore Data in Swift - The Comprehensive Guide, in which I explain how to map the most common data structures between Swift and Firestore.
- Please don't use the force unwrap operator - your app will crash if the unwrapped object is nil. In this instance, using Codable will help you to avoid using this operator, but in other cases you should consider using
if let
, guard let
, optional unwrapping with and without default values. Check out Optionals In Swift: The Ultimate Guide – LearnAppMaking for more details.
- I would strongly recommend using view models to encapsulate your data access logic, especially when accessing Firestore or any other remote backend. Check out this code to see how this can be done.