When I compile I get this error "The path to the document cannot be empty".
To fix this, I should add an unescaped closure. How to add an unescaped closure to the fetchUsers () function and call the GetCorsodiLaurea () function in the closure? In such a way, the compiler will not try to execute functions asynchronously.
LoginViewModel
import SwiftUI
import Firebase
import LocalAuthentication
class LoginViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@AppStorage("use_face_id") var useFaceID: Bool = false
@AppStorage("use_face_email") var faceIDEmail: String = ""
@AppStorage("use_face_password") var faceIDPassword: String = ""
//Log Status
@AppStorage("log_status") var logStatus: Bool = false
//MARK: Error
@Published var showError: Bool = false
@Published var errorMsg: String = ""
// MARK: Firebase Login
func loginUser(useFaceID: Bool,email: String = "",password: String = "")async throws{
let _ = try await Auth.auth().signIn(withEmail: email != "" ? email : self.email, password: password != "" ? password : self.password)
DispatchQueue.main.async {
if useFaceID && self.faceIDEmail == ""{
self.useFaceID = useFaceID
// MARK: Storing for future face ID Login
self.faceIDEmail = self.email
self.faceIDPassword = self.password
}
self.logStatus = true
}
}
//MARK: FaceID Usage
func getBioMetricStatus()->Bool{
let scanner = LAContext()
return scanner.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: .none)
}
// MARK: FaceID Login
func autenticationUser()async throws{
let status = try await LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "To Login Into App")
if status{
try await loginUser(useFaceID: useFaceID,email: self.faceIDEmail,password: self.faceIDPassword)
}
}
}
ProfileViewModel
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
import SwiftUI
class ProfileViewModel: ObservableObject {
@Published var userInfo: UserModel = .empty
@Published var userDegree: userDegreeModel = .empty
@Published var isSignedIn = false
@Published var showError: Bool = false
@Published var errorMsg: String = ""
var uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.uid = user.uid
self.isSignedIn = true
}
else {
self.isSignedIn = false
self.userInfo.Nomeintero = ""
}
}
fetchUser() { [self] in
fetchDegrees()
}
}
func fetchUser(completion: @escaping () -> Void) {
let docRef = db.collection("users").document(uid)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMsg = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.userInfo = try document.data(as: UserModel.self)!
completion()
}
catch {
print(error)
}
}
}
}
}
func fetchDegrees() {
let docRef = db.collection("DegreeCourses").document(userInfo.Tipocorso)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMsg = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.userDegree = try document.data(as: userDegreeModel.self)!
}
catch {
print(error)
}
}
}
}
}
}
UserModel
import SwiftUI
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
public struct UserModel: Codable{
@DocumentID var id: String?
var Nome : String
var Cognome : String
var photoURL : String
var Nomeintero : String
var Corsodilaurea : String
var Tipocorso : String
}
extension UserModel {
static let empty = UserModel(Nome: "", Cognome: "", photoURL: "", Nomeintero: "", Corsodilaurea: "", Tipocorso: "")
}
userDegreeModel
import SwiftUI
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
struct userDegreeModel: Codable {
@DocumentID var id: String?
var Name : String
var TotalSubjects : Int
}
extension userDegreeModel {
static let empty = userDegreeModel(Name: "", TotalSubjects: 0)
}
CodePudding user response:
A couple of notes:
- Firestore calls return on the main dispatch queue already about this), so you don't need to manually switch to the main queue using
DispatchQueue.async { }
. See my Twitter thread for more details. - Instead of mapping Firestore documents manually, you can use Codable to do so. This means less code to write, and fewer typos :-) Here is an article that goes into much more detail: Mapping Firestore Data in Swift - The Comprehensive Guide | Peter Friese
- Accessing the signed in user using
Auth.auth().currentUser!.uid
might result in you app breaking if no user is signed in. I recommend implementing an authentication state listener instead. - Since all of Firebase's APIs are asynchronous (see my blog post about this: Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese), the result of
fetchUser
will take a short moment, so you want to make sure to only callfetchDegrees
once that call has completed. One way to do this is to use a completion handler.
Lastly, I recommend following a styleguide like this one for naming your classes and attribute: Swift Style Guide
I've updated your code accordingly below.
import Firebase
import FirebaseDatabase
import FirebaseFirestoreSwift
import SwiftUI
public struct UserModel: Codable {
@DocumentID var id: String?
var firstName: String
var lastName: String
var photoUrl: String
// ...
}
extension UserModel {
static let empty = UserModel(firstName: "", lastName: "", photoUrl: "")
}
public struct UserDegreeModel: Codable {
@DocumentID var id: String?
var name: String
var totalSubjects: Int
// ...
}
extension UserDegreeModel {
static let empty = UserDegreeModel(name: "", totalSubjects: 0)
}
class ProfileViewModel: ObservableObject {
@Published var userInfo: UserModel = .empty
@Published var userDegree: UserDegreeModel = .empty
@Published var isSignedIn = false
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.uid = user.uid
self.isSignedIn = true
}
else {
self.isSignedIn = false
self.username = ""
}
}
fetchUser() {
fetchDegrees()
}
}
func fetchUser(completion: () -> Void) {
let docRef = db.collection("users").document(uid)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.user = try document.data(as: UserModel.self)
completion()
}
catch {
print(error)
}
}
}
}
}
func fetchDegrees() {
let docRef = db.collection("DegreeCourses").document(userInfo.Tipocorso)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.userDegree = try document.data(as: UserDegreeModel.self)
}
catch {
print(error)
}
}
}
}
}
}