0

I am working on a recipe-app connected to firestore and have trouble reading the data saved in the database. I save a recipe that consists of title, id etc but it also contains an array of ingredients. This array is a struct containing id, name and amount. I am able to get the recipe object but the array of ingredients is empty. This is how is get the recipe

    private func listenForRecipes() {
        
        db.collection("recipe").addSnapshotListener { (querySnapshot, error) in
            guard let documents = querySnapshot?.documents else {
                print("No documents")
                return
            }
            
            self.recipes = documents.map { queryDocumentSnapshot -> RecipePost in
                let data = queryDocumentSnapshot.data()
                let title = data["title"] as? String ?? ""
                let steps = data["steps"] as? [Step] ?? []
                let ingredients = data["ingredients"] as? [Ingredient] ?? []
                let serves = data["serves"] as? Int ?? 0
                let author = data["author"] as? String ?? ""
                let authorId = data["authorId"] as? String ?? ""
                let category = data["category"] as? String ?? ""
                let image = data["image"] as? String ?? ""
                print("\(ingredients)")

                return RecipePost(title: title, steps: steps, ingredients: ingredients, serves: serves, author: author, authorId: authorId, category: category, image: image)
                
            }
        }
    }

Thankful for any help.

  • This is actually simple to correct. If your ingredients are an string array you can simply do `let ingredients = dataSnapshot?.get("ingredients") as? [String] ?? [String]()`. And that will generate an string array of ingredients. To expand on that you can also get other data but Firestore does not have a field type of Struct (per your question) so we would need to know what that actually looks like. It should be straightforward but more data is needed. – Jay Feb 12 '21 at 18:36

1 Answers1

2

The data that you're getting from Firebase is coming back to you in the form of a [String:Any] dictionary. Your current code is taking those dictionary keys (title, author, etc) and doing optional casts (the as?), telling the system "if this data is actually a String, then set my variable to that value. If not (??), here's the default value to use instead"

The problem comes when you introduce custom types. The system doesn't inherently know that your item is an Ingredient or Step. So, the cast fails, and you get the default value of [].

You have two options:

  1. Use a custom type for your entire document (see Firebase documentation on this here: https://firebase.google.com/docs/firestore/query-data/get-data#swift_3). This SO question is also relevant: How to convert document to a custom object in Swift 5?

  2. Convert the [String:Any] dictionary (or array of dictionaries as it may be in this case) yourself. First step might be to print data["ingredients"] to the console to see what it really has in it. Without being able to see what you actually have in Firestore, Let's assume it is a [[String:Any]] (an array of dictionaries). Then your conversion might look like this:

let ingredients = (data["ingredients"] as? [[String:Any]]).map { item in
  return Ingredient(id: item["id"] as? String ?? "", name: item["name"] as? String ?? "", amount: item["amount"] as? String ?? "")
}

You can also experiment with using Codable, which could allow you to automate some of this process, say with JSONDecoder to do some of the work for you. Relevant SO: How can I use Swift’s Codable to encode into a dictionary?

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thanks for answering!I struggled getting that to work and ended up saving ingredient and steps in separate collections under the same document as recipe. – Olle Hammar Feb 11 '21 at 13:06