1

I want to implement a Text field that displays the current user's existing score in the DB (Firestore). Because of the nature of async in Firebase query, I also need to do some adjustment in my codes. However, it seems that completion() handler does not work well:

// ViewModel.swift import Foundation import Firebase import FirebaseFirestore class UserViewModel: ObservableObject { let current_user_id = Auth.auth().currentUser!.uid private var db = Firestore.firestore() @Published var xp:Int? func fetchData(completion: @escaping () -> Void) { let docRef = db.collection("users").document(current_user_id) docRef.getDocument { snapshot, error in print(error ?? "No error.") self.xp = 0 guard let snapshot = snapshot else { completion() return } self.xp = (snapshot.data()!["xp"] as! Int) completion() } } } 
// View.swift import SwiftUI import CoreData import Firebase { @ObservedObject private var users = UserViewModel() var body: some View { VStack { HStack { // ... Text("xp: \(users.xp ?? 0)") // Text("xp: 1500") .fontWeight(.bold) .padding(.horizontal) .foregroundColor(Color.white) .background(Color("Black")) .clipShape(CustomCorner(corners: [.bottomLeft, .bottomRight, .topRight, .topLeft], size: 3)) .padding(.trailing) } .padding(.top) .onAppear() { self.users.fetchData() } // ... } } 

My result kept showing 0 in Text("xp: \(users.xp ?? 0)"), which represents that the step is yet to be async'ed. So what can I do to resolve it?

4
  • 1
    You're not showing where fetchData is called. Also, are you sure that self.xp is getting assigned? Have you set break points or print statements to make sure you're getting the value you expect? Commented Apr 23, 2021 at 21:20
  • It's also not really clear what the completion handler is supposed to be doing in this case. Your view will automatically respond to the @Published property, further reinforcing the idea that the code probably is not actually successfully reaching or assigning self.xp Commented Apr 23, 2021 at 21:37
  • Well, I just believe because Firebase always retrieves data asynchronously, completion() handler is helpful to indicate that this step needs to be done before any other. Do u mean that completion() is useless here? Commented Apr 23, 2021 at 21:56
  • You call site that you've added in your edit doesn't even seem to be valid (it doesn't provide an argument for the completion handler. So, yes, for that reason and what I mentioned above, it does seem to be useless here. Commented Apr 23, 2021 at 22:00

1 Answer 1

1

I would first check to make sure the data is valid in the Firestore console before debugging further. That said, you can do away with the completion handler if you're using observable objects and you should unwrap the data safely. Errors can always happen over network calls so always safely unwrap anything that comes across them. Also, make use of the idiomatic get() method in the Firestore API, it makes code easier to read.

That also said, the problem is your call to fetch data manually in the horizontal stack's onAppear method. This pattern can produce unsavory results in SwiftUI, so simply remove the call to manually fetch data in the view and perform it automatically in the view model's initializer.

class UserViewModel: ObservableObject { @Published var xp: Int? init() { guard let uid = Auth.auth().currentUser?.uid else { return } let docRef = Firestore.firestore().collection("users").document(uid) docRef.getDocument { (snapshot, error) in if let doc = snapshot, let xp = doc.get("xp") as? Int { self.xp = xp } else if let error = error { print(error) } } } } struct ContentView: View { @ObservedObject var users = UserViewModel() var body: some View { VStack { HStack { Text("xp: \(users.xp ?? 0)") } } } } 

SwiftUI View - viewDidLoad()? is the problem you ultimately want to solve.

Sign up to request clarification or add additional context in comments.

10 Comments

Honestly I cannot see any difference between your answer and mine. Not to mention that your declaration using guard let is illegal.
Then you have a data problem, a network problem, or an authorization problem so remove code from the list and continue debugging.
Illegal to who?
Make sure currentUser? is optionally chained and not force unwrapped like you had it: guard let uid = Auth.auth().currentUser?.uid
Read the most-upvoted comment in the accepted answer and you will see where you need to solve your problem ultimately, finding the correct place to invoke the call to fetch data (if the initializer is not where you want to do it). stackoverflow.com/questions/56496359/swiftui-view-viewdidload
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.