0

I try to parse and assign data, which I am becoming from Firebase The structure in Firebase looks like this:

enter image description here

I try to fetch data from database and assign it to instance of class Meal:

ref = Database.database().reference()
        databaseHandle = ref.child("Meal").observe(.value, with: { (snapshot) in
            var downloadedName : String!
            var downloadedPhoto : String!
            var downloadedRating : Int!
            var downloadedSteps : Array <String>!
            var downloadedIngredients : [Dictionary <String, String>]!

            print(snapshot.value)
            if let dict = snapshot.value as? Dictionary<String, Any>{
                print("VALUES!!!")


                for key in dict.keys {

                    if let values = dict[key] as? Dictionary<String, Any> {
                        print(values)

                        if let name = values["name"] as? String{
                            downloadedName = name
                        }

                        if let photo = values["photo"] as? String{
                            downloadedPhoto = photo
                        }

                        if let rating = values["rating"] as? Int{
                            downloadedRating = rating
                        }
                        if let steps = values["steps"] as? Array<String>{
                            downloadedSteps = steps
                        }
                        if let ingredients = values["ingredients"] as? [Dictionary <String, String>]{
                            downloadedIngredients = ingredients
                        }

                         let meal = Meal(name: downloadedName, photo: UIImage(named: downloadedPhoto), rating: downloadedRating, steps: downloadedSteps, ingredients: downloadedIngredients)           
                        self.meals.append(meal!);
                    }
                }

The Meal class itself looks like this:

class Meal {
    var name: String
    var photo: UIImage?
    var rating: Int
    var steps: Array<String>
    var ingredients: [Dictionary<String, String>]}

I get the first print - the whole data, so the connection with DB is OK, but as i try to assign it - nothing happens, no errors, no data (the second print with message VALUES!!! is not shown at all, what am I doing wrong?

Here is also what I get by first print

Optional(<__NSArrayM 0x600002fdaa60>(
{
    ingredients =     (
                {
            amount = 100;
            ingredient = milk;
            measurement = ml;
        },
                {
            amount = 120;
            ingredient = milk;
            measurement = ml;
        }
    );
    name = "Caprese Salad";
    photo = meal1;
    rating = 4;
    steps =     (
        test1,
        test11
    );
},
{
    ingredients =     (
                {
            amount = 100;
            ingredient = milk;
            measurement = ml;
        },
                {
            amount = 120;
            ingredient = milk;
            measurement = ml;
        }
    );
    name = "Chicken and Potatoes";
    photo = meal2;
    rating = 3;
    steps =     (
        test2,
        test22
    );
},
{
    ingredients =     (
                {
            amount = 100;
            ingredient = milk;
            measurement = ml;
        },
                {
            amount = 120;
            ingredient = milk;
            measurement = ml;
        }
    );
    name = "Pasta with Meatballs";
    photo = meal3;
    rating = 2;
    steps =     (
        test3,
        test33
    );
}
)
)

So, I assume, I retrieve the data in the false way at some point, how could i fix it?

7
  • You should use the Codable protocol. Commented Nov 28, 2018 at 13:40
  • if let dict = snapshot.value as? Dictionary<String, Any>{}, so if snapshot.value is not a Dictionary where keys are String and values are "whatever", you won't pass that test. Alright? Print of "values": starts with Optional(<__NSArrayM 0x600002fdaa60>(. Yeah, clearly that's an Array, not a Dictionary. So because of ` as? Dictionary<String, Any>`, you don't get your second print. Commented Nov 28, 2018 at 13:50
  • @Larme What should I change in my data to make it as Dictionary?.. Commented Nov 28, 2018 at 14:08
  • No. You misunderstood the structure of snapshot.value. So instead of trying to parse it as it was a Dictionary, parse it as it is an Array. Commented Nov 28, 2018 at 14:09
  • 1
    pastebin.com/RTC14wRr should do the trick. Parsing an array of dictionary, not a Dictionary, which is the real top level of your response. Also, avoid force unwrapp (using !), and the var declaration before the loop. Because currently if you missed an if let, you'd get the previous value, not a "default one". But you might want to use Codable (Swift 4+). Edit: Replace values["someString"] with aValue["someString"], small copy/paste mistake. Commented Nov 28, 2018 at 14:53

1 Answer 1

2

You have:

print(snapshot.value)
if let dict = snapshot.value as? Dictionary<String, Any>{
    print("VALUES!!!")
    ....
}

You say that print(snapshot.value) is called but not print("VALUES!!!"). Well, that means that snapshot.value or it isn't a Dictionary which keys are String objects and values are of type Any.

Now, let's see the output of snapshot.value:

Optional(<__NSArrayM 0x600002fdaa60> ...

NSArrayM => NSMutableArray, so snapshot.value is an Array, not a Dictionary. Of course then the as? Dictionary will fail!

So you need to treat it as an Array.

Quickly written:

if let array = snapshot.value as? [[String: Any]] {
    for aValue in array {
        let name = aValue["name"] as? String ?? "unnamed"
        let photoName = aValue["photo"] as? String ?? "noPhoto"
        let rating = aValue["rating"] as? Int ?? 0
        let steps = aValue["steps"] as? [String] ?? []
        let ingredients = aValue["ingredients"] as? [[String: String]] ?? [:]
        let meal = Meal(name: name, photo: UIImage(named: photoName), rating: rating, steps: steps, ingredients: ingredients)           
        self.meals.append(meal)
    }
}

What could also been wrong with your approach:

var downloadedName : String!
loop {
    if let name = values["name"] as? String {
        downloadedName = name
    }
    let meal = Meal(name: downloadedName, ...)
}

Well, if for the second value you didn't have a name (either because it's not a String or because the value doesn't exist), you downloadedName would have the value of the first one, but the rest of the values of the second one.

I used the approach:

let name = aValue["name"] as? String ?? "unnamed"

Meaning, that if it's nil, it gets a default value. But you could decide to accept only valid one, by a guard let or if let potentially on all the subvalues.

For instance:

if let name = aValue["name"] as? String,
   let let photoName = aValue["photo"] as? String, 
   ... {
    let meal = Meal(name: name, photo: UIImage(named: photoName)
    self.meals.append(meal)
}

And since your Meal.init() seems to return an optional, you could add it also in the if let and avoid the force unwrap of meal.

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

Comments

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.