0

I have tried my first SwiftUI Project. I only want to show some data stored in Firestore (Google Firebase). Here is my code:

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct MonsterObj: Identifiable, Equatable, Hashable {
    var id = UUID()
    var name: String
    var element: String
    var immune: String
    var size: String
    var twoStarWeakness: String
    var threeStarWeakness: String

    #if DEBUG
    static let exampleMonster = MonsterObj(id: UUID(), name: "Test Monster", element: "Test Element", immun: "Test immun", groesse: "Test groesse", twoStarWeakness: "Test 2 Weakness", threeStarWeakness: "Test3 Weakness")

    #endif

}

class MonsterC: ObservableObject {

    @Published var monsters = [MonsterObj]()

    init() {
        let db = Firestore.firestore()
        var monsterNames: [String] = []

        db.collection("Monster").getDocuments() { (querySnapshot, err) in
            if let err = err {
                print(err)
            } else {
                for document in querySnapshot!.documents {
                    monsterNames.append("\(document.documentID)")
                    print("document: \(document.documentID)")

                }
            }
        }

            for monsterName in monsterNames {
                print(monsterName)
                db.collection("Monster").document(monsterName).getDocument { (document, error) in
                    if let document = document, document.exists {
                        let elementGetter = document.get("element") as! String
                        let immuneGetter = document.get("immune") as! String
                        let sizeGetter = document.get("size") as! String
                        let twoStarWeaknessGetter = document.get("2 star weakness") as! String
                        let threeStarWeaknessGetter = document.get("3 star weakness")as! String

                        self.monsters.append(MonsterObj(name: monsterName, element: elementGetter, immune: immuneGetter, size: sizeGetter, twoStarWeakness: twoStarWeaknessGetter, threeStarWeakness: threeStarWeaknessGetter))

                        }
                }
            }
     }
}

This is my View:

import SwiftUI  

struct ContentView: View {  
    @EnvironmentObject var monsterT: MonsterC  

    var body: some View {  
        List(monsterT.monsters, id: \.self) { monster in  
            Text(monster.name)  
        }  
    }  
} 

And I did following to SceneDelegate.swift:

var window: UIWindow?  
var monsterT = MonsterC()  
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions {


    // Create the SwiftUI view that provides the window contents.  
    let contentView = ContentView().environmentObject(monsterT)  


    if let windowScene = scene as? UIWindowScene {  
        let window = UIWindow(windowScene: windowScene)  
        window.rootViewController = UIHostingController(rootView: contentView)  
        self.window = window  
        window.makeKeyAndVisible()  
    }  
}

So my Problem is the list is empty. I figured out in init of class MonsterC the line monsterNames.append("\document.documentID)") does not append anything to monsterNames. But print("document: \(document.documentID)") is printing all monsterNames.

My google Firestore structure looks like this:

    Collection -> Document -> Fields
-------------------------------------------
    Monster -> Anjanath -> immune: fire,
                           element: fire

etc. There's only one collection ("Monster").

Can anyone explain to a beginner why .append is not working here but print is doing everything right?

1
  • ANd if you print print("After monstersNames.append: \monsterNames)") Could it be because you are missing the asychrone concept? Commented Apr 24, 2020 at 18:21

1 Answer 1

2

You need to understand calling asynchronous functions. My advice is to restructure your code, it is not a good idea to do these async calls in your init().

Your function "db.collection("Monster").getDocuments() { (querySnapshot, err) in ..." is asynchronous. You must either wait till it is finished to use the results, or do what you need inside the function. Note you also have another async function "db.collection("Monster").document(monsterName).getDocument {"

So .append is not working because the results of your function "db.collection("Monster").getDocuments() { (querySnapshot, err) in ..." are not available when you do the .append.

So if you must use this dodgy code, try this to fix your array problem:

class MonsterC: ObservableObject {

@Published var monsters = [MonsterObj]()

init() {
    let db = Firestore.firestore()

    db.collection("Monster").getDocuments() { (querySnapshot, err) in
        if let err = err {
            print(err)
        } else {
            var monsterNames: [String] = []
            for document in querySnapshot!.documents {
                monsterNames.append("\(document.documentID)")
                print("document: \(document.documentID)")

            }

            for monsterName in monsterNames {
                print(monsterName)
                db.collection("Monster").document(monsterName).getDocument { (document, error) in
                    if let document = document, document.exists {
                        let elementGetter = document.get("element") as! String
                        let immuneGetter = document.get("immune") as! String
                        let sizeGetter = document.get("size") as! String
                        let twoStarWeaknessGetter = document.get("2 star weakness") as! String
                        let threeStarWeaknessGetter = document.get("3 star weakness")as! String

                        self.monsters.append(MonsterObj(name: monsterName, element: elementGetter, immune: immuneGetter, size: sizeGetter, twoStarWeakness: twoStarWeaknessGetter, threeStarWeakness: threeStarWeaknessGetter))

                    }
                }
            }

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

3 Comments

Thanks for the information about asynchronous functions. You called it "dodgy code" so I guess it's bad to do like this. Can you tell me how to make better?
sorry for calling the code dodgy. Good question, how to make the code better. However it is beyond me without understanding what you are trying to achieve. To me init() is for creating/preparing the class and initialising any stored properties. To update/fill the properties I tend to do it outside of init() especially if it involves async functions. Something like a static func createMonsterClass(...) with a callback in case there was an error in the fetching. When you do all this in init() you never know if there was and error or if the object is ready when you want to use it.
Since you've imported FirebaseFirestoreSwift, you can make the mapping even easier by using queryDocumentSnapshot.data(as: MonsterObj.self)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.