good night sorry for my bad English, I'm studying swift async/await, task and dispatch. Well I got a block of code that performed google login had several callbacks, so I tried to transform the whole method into async.
My problem is that when I call the refactored method and apply async/await I get the error when performing self.navigationController?.dismiss(animated: true, completion: nil), with a breakpoint `Thread 14: "Call must be made on main thread".
I understand what he means, but how do I run .dimiss on the main thread? I say I'm no longer in the main thread? At what point it came out of it, I didn't quite understand it.
This is my code with the chained callbacks, it works fine.
guard let clientId = FirebaseApp.app()?.options.clientID else {
print("failed get clientId google signin")
return
}
let config = GIDConfiguration(clientID: clientId)
GIDSignIn.sharedInstance.signIn(with: config, presenting: self, callback: { [weak self] authResult, error in
guard let strongSelf = self else {
return
}
guard authResult != nil, error == nil else {
if let error = error {
print("failed login with google sign in \(error)")
}
return
}
guard let authentication = authResult?.authentication,
let idToken = authentication.idToken else {
return
}
guard let email = authResult?.profile?.email,
let firstName = authResult?.profile?.givenName,
let lastName = authResult?.profile?.familyName else {
return;
}
DatabaseManager.shared.userExists(with: email, completion: { exists in
if !exists {
//insert database
let chatUser = ChatAppUser(firtName: firstName, lastName: lastName, emailAddress: email)
DatabaseManager.shared.insertUser(with: chatUser, completion: { success in
if success {
//upload image
if ((authResult?.profile?.hasImage) != nil) {
guard let url = authResult?.profile?.imageURL(withDimension: 200) else {
return
}
URLSession.shared.dataTask(with: url, completionHandler: {data, _, _ in
guard let data = data else {
return
}
let fileName = chatUser.profilePictureFileName
StorageManager.shared.uploadProfilePicture(with: data, fileName: fileName, completion: { result in
switch result {
case .success (let downloadUrl):
UserDefaults.standard.set(downloadUrl, forKey: "profile_picture_url")
print(downloadUrl)
case .failure(let error):
print("Storage manager error: \(error)")
}
})
}).resume()
}
}
})
}
})
let credentials = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credentials, completion: {authResult, error in
guard authResult != nil, error == nil else {
if let error = error {
print("google credential login failed, MFA may be needed \(error)")
}
return;
}
print("successFully logged user in")
strongSelf.navigationController?.dismiss(animated: true, completion: nil)
})
})
This is my code with async/await applied.
guard let clientId = FirebaseApp.app()?.options.clientID else {
print("failed get clientId google signin")
return
}
let config = GIDConfiguration(clientID: clientId)
do {
let resultSignin = try await GoogleManager.shared.googleSignin(config: config, presenting: self)
switch resultSignin {
case .success(let autheResult):
let authentication = autheResult.authentication
guard let idToken = authentication.idToken else {
break
}
//function authentication
guard let email = autheResult.profile?.email,
let firstName = autheResult.profile?.givenName,
let lastName = autheResult.profile?.familyName else {
break
}
let ifExist = try await DatabaseManager.shared.userExists(with: email)
if ifExist {
let chatUser = ChatAppUser(firtName: firstName, lastName: lastName, emailAddress: email)
let resultInsert = try await DatabaseManager.shared.insertUser(with: chatUser)
if resultInsert {
if autheResult.profile?.hasImage != nil {
guard let url = autheResult.profile?.imageURL(withDimension: 200) else{
break
}
let dataPicture = await ApiManager.shared.getData(url: url)
guard let dataPicture = dataPicture else {
break
}
let fileName = chatUser.profilePictureFileName
let resultUpload = try await StorageManager.shared.uploadProfilePicture(with: dataPicture, fileName: fileName)
switch resultUpload {
case .success (let downloadUrl):
UserDefaults.standard.set(downloadUrl, forKey: "profile_picture_url")
print(downloadUrl)
break
case .failure(let error):
print("Storage manager error: \(error)")
alertUserLoginError(message: "Storage manager error: \(error)")
break
}
}
}
}
let credentials = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: authentication.accessToken)
let authResult = try await Auth.auth().signIn(with: credentials)
print("Logged In user: \(authResult.user)")
self.navigationController?.dismiss(animated: true, completion: nil)
break
case .failure(let error):
alertUserLoginError(message: "Error: \(error)")
break
}
}catch let error as NSError {
print("Try catch error: \(error)")
alertUserLoginError(message: "Try catch error: \(error)")
}
googleSignin
final class GoogleManager {
static let shared = GoogleManager()
private let googleInstance = GIDSignIn.sharedInstance
}
extension GoogleManager {
public func googleSignin(config: GIDConfiguration, presenting: UIViewController, completion: @escaping (Result<GIDGoogleUser,Error>) -> Void){
googleInstance.signIn(with: config, presenting: presenting, callback: { authResult, error in
guard authResult != nil, error == nil else {
completion(.failure(error!))
return
}
completion(.success(authResult!))
})
}
public func googleSignin(config: GIDConfiguration, presenting: UIViewController) async throws -> (Result<GIDGoogleUser, Error>) {
await withCheckedContinuation { continuation in
googleSignin(config: config, presenting: presenting, completion: { result in
continuation.resume(returning: result)
})
}
}
}
Stack trace:
Main Thread Checker: UI API called on a background thread: -[UIViewController navigationController]
PID: 426, TID: 18401, Thread name: (none), Queue name: Swift global concurrent queue, QoS: 25
Backtrace:
4 Messenger 0x0000000102dc0360 $s9Messenger19LoginViewControllerC19googleButtonClicked022_498C1017229A4D9B461B7L9B38EF23C9LLyyYaFTQ5_