I am using Firestore to retrieve some data about a user and just update a label to be the user's first name on viewDidLoad of the Home screen. However, I learned the label was getting loaded/updated before even receiving the data.
I knew I had to wait until we fully received the data from Firestore. I feel like completion handlers were my best bet and I feel like I have followed Swift completion handler documentation to a T, but I can't seem to get it to work! If someone could please tell me what I am doing wrong here. One important thing to keep in mind here is before I make HomeViewController the rootview controller and present it to the user after logging in, I am retrieving the Google firestore DocumentReference and setting userProfileDoc to this reference to be used by the HomeViewController class in a different method.
class HomeViewController: UIViewController {
var userProfileDoc: DocumentReference?
private var currentUser: UserProfile?
@IBOutlet weak var userFirstName: UILabel!
@IBOutlet weak var logoutButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let myCompletionHandler: (UserProfile) -> Void = { theUser in
self.userFirstName.text = "Welcome, " theUser.userFirstName!
self.userFirstName.sizeToFit()
}
initializeUserProfile(using: myCompletionHandler)
}
func initializeUserProfile(using completionHandler: (UserProfile) -> Void) {
self.currentUser = UserProfile(userProfileDoc!)
completionHandler(self.currentUser!)
}
}
`
Below is the constructor of UserProfile `
class UserProfile {
var userFirstName: String?
var userLastName: String?
var profilePicURL: String?
var UID: String?
init(_ documentRef: DocumentReference) {
documentRef.getDocument { [self] document, error in
if let error = error as NSError? {
print("Error getting document: \(error.localizedDescription)")
}
else {
if let document = document {
let data = document.data()
userFirstName = data?["firstName"] as? String ?? ""
userLastName = data?["lastName"] as? String ?? ""
profilePicURL = data?["profilePicURL"] as? String ?? ""
UID = data?["uid"] as? String ?? ""
}
}
}
}
}
CodePudding user response:
Don't make the asynchronous call in the init but move it to initializeUserProfile
instead and call the completion handler from inside the closure of the asynchronous call like below.
func initializeUserProfile(_ documentRef: DocumentReference, using completionHandler: (UserProfile) -> Void) {
documentRef.getDocument { [self] document, error in
if let error = error as NSError? {
print("Error getting document: \(error.localizedDescription)")
} else {
if let document = document {
let data = document.data()
let userFirstName = data?["firstName"] as? String ?? ""
let userLastName = data?["lastName"] as? String ?? ""
let profilePicURL = data?["profilePicURL"] as? String ?? ""
let userID = data?["uid"] as? String ?? ""
completionHandler(UserProfile(userFirstName: userFirstName,
userLastName: userLastName,
profilePicURL: profilePicURL,
UID: userID))
}
}
}
}
and call it from viewDidLoad
initializeUserProfile(userProfileDoc, using: myCompletionHandler)
Note that I don't use Firebase myself so I haven't been able to compile the code given here but it should be enough to explain how I think this should be solved and this also mean I don't know if there are other improvements that should be done.
For completeness, this is how I changed the UserProfile
class
class UserProfile {
var userFirstName: String?
var userLastName: String?
var profilePicURL: String?
var userID: String?
init(userFirstName: String?, userLastName: String?, profilePicURL: String?, userID: String?) {
self.userFirstName = userFirstName
self.userLastName = userLastName
self.profilePicURL = profilePicURL
self.userID = userID
}
}