firebase & swift - this class is not key value coding-compliant for the key -----

Question!

I know there are other questions like this but I think my problem is with how I'm accessing firebase and not an outlet because my error is in an @IBAction function that is able to be called before the error happens.

@IBAction func sign_in_out_tapped(sender : UIButton) {
    if let op_user = user {
        if (op_user.user_ref?.valueForKey("current_status"))! as! String == "OUT" {
            let sign_in_time = NSDate()
            op_user.user_ref?.childByAppendingPath("logins").updateChildValues([String(op_user.user_ref?.valueForKey("num_of_logins")! as! Int + 1): ["sign_in_time": sign_in_time]])
            signinout = SignInOutModel(sign_in_time: sign_in_time)

            op_user.user_ref?.updateChildValues(["current_status": "IN"])
        } else {
            signinout!.sign_out_time = NSDate()
            op_user.user_ref?.childByAppendingPath("logins").childByAppendingPath(String(user?.user_ref?.valueForKey("num_of_logins"))).updateChildValues(["sign_out_time": signinout!.sign_out_time!])

            signinout!.duration = (op_user.user_ref?.childByAppendingPath("logins").childByAppendingPath(String(user?.user_ref?.valueForKey("num_of_logins"))).valueForKey("sign_in_time")?.timeIntervalSinceNow)!
            op_user.user_ref?.childByAppendingPath("logins").childByAppendingPath(String(user?.user_ref?.valueForKey("num_of_logins"))).updateChildValues(["duration": signinout!.duration])
            op_user.user_ref?.updateChildValues(["total_hours": (Double((op_user.user_ref?.valueForKey("total_hours"))! as! NSNumber) + signinout!.duration)])
        }
} else {
        let sign_in_alert = UIAlertController(title: "Sign in.", message: "What is your first and last name?", preferredStyle: UIAlertControllerStyle.Alert)

        sign_in_alert.addTextFieldWithConfigurationHandler { textField in
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MainViewController.handleTextFieldTextDidChangeNotification(_:)), name: UITextFieldTextDidChangeNotification, object: textField)
        }

        func removeTextFieldObserver() {
            NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: sign_in_alert.textFields![0])
        }

        let cancel_action = UIAlertAction(title: "Cancel", style: .Cancel) { action in
            print("Sign In Cancel Button Pressed")
            removeTextFieldObserver()
        }

        let save_action = UIAlertAction(title: "Save", style: .Default) { action in
            print("Sign Out Save Button Pressed")

            let textField = sign_in_alert.textFields![0] as UITextField

            if let user_name = textField.text {
                var not_found = false

                self.students_ref.childByAppendingPath(user_name).observeEventType(.Value, withBlock: { snapshot in

                    if (snapshot.value is NSNull) {
                        not_found = true
                    } else {
                        self.user = User(user_name: user_name)
                        self.user?.user_ref = self.students_ref.childByAppendingPath(user_name)
                        self.refresh_user_name_label()
                    }
                })

                if !not_found {
                    self.mentors_ref.childByAppendingPath(user_name).observeEventType(.Value, withBlock: { snapshot in

                        if (snapshot.value is NSNull) {
                            not_found = true
                        } else {
                            self.user = User(user_name: user_name)
                            self.user?.user_ref = self.mentors_ref.childByAppendingPath(user_name)
                            self.refresh_user_name_label()
                        }
                    })
                } else {
                    self.error_message("User not found. Please update Firebase.")
                }
            } else {
                self.error_message("Could not sign in.")
            }

            removeTextFieldObserver()
        }

        save_action.enabled = false

        AddAlertSaveAction = save_action

        sign_in_alert.addAction(cancel_action)
        sign_in_alert.addAction(save_action)

        self.presentViewController(sign_in_alert, animated: true, completion: nil)

        if let _ = user {
            let sign_in_time = NSDate()
            signinout = SignInOutModel(sign_in_time: sign_in_time)
        }
    }

    refresh_sign_in_out_button()
}

I believe the error is at the top where it says "op_user.user_ref?.valueForKey("current_status"))! as String == "OUT"" because not only does the error say,

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Firebase 0x7fa12e0b5530> valueForUndefinedKey:]: this class is not key value coding-compliant for the key current_status.'

but when going through the debugger, the program didn't terminate until "valueForKey("current_status")".

Any help would be appreciated! Thank you!

EDIT: My firebase:

{
  "mentors" : {
    "Ash Dreyer" : {
      "current_status" : "IN",
      "num_of_logins" : 0,
      "total_hours" : 0
    },
    "Donald Pinckney" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    },
    "Jasmine Zhou" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    },
    "Michael Corsetto" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    }
  },
  "students" : {
    "Bryton Moeller" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    },
    "Kelly Ostrom" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    },
    "Kyle Stachowicz" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    },
    "Wesley Aptekar-Cassels" : {
      "current_status" : "OUT",
      "num_of_logins" : 0,
      "total_hours" : 0
    }
  }
}

EDIT:

The goal of my project is to create a sign in/out app. My mentor wants others to be able to see if someone is signed in or not and wants to track how long in total someone has been signed in (like if they have reached 100 hours at the shop or something.)



Answers

Based on your follow up comments, here's a few things that may help. Pardon the long-winded answer, but just trying to help reduce and simplify the amount of code you are writing.

In general, disassociating keys from data is a good model. The reason for that is: what if a user changes their name? They get married so a different last name, decide they like to be called Jim instead of James etc. If you use their name as the key, it's not changeable and anywhere else you refer to it will be stuck with that as well. If you make the name a child node, you can change it any time without affecting other data.

The first step is to change the structure to something like this

{
  "mentors" : {
     "uid_0" : {
       "user_name": "Ash Dreyer",
       "current_status" : "IN",
       "num_of_logins" : 0,
       "total_hours" : 0
    },
     "uid_1" : {
       "user_name": "Donald Pinckney",
       "current_status" : "OUT",
       "num_of_logins" : 0,
       "total_hours" : 0
    },

The uid_0, uid_1 etc are created by Firebase when the user is created and can be retrieved from authData.uid. That's a great piece of data to use at the key for each user you store in your /mentors (or /users) node.

When the user signs in, you have a couple of options: if you don't need their data, you can just update the current_status node. You know the specific node name as it's their auth.uid.

let ref = /mentors node
let thisUser = ref.childByAppendingPath(auth.uid)
let thisUsersStatusRef = thisUser.childByAppendingPath("current_status")
thisUsersStatusRef.setValue("In")

and do the same thing for the timestamp.

If you need to display their name in the UI, just read their data via an observeSingleEvent, and update the current status and timestamp.

let ref = /mentors node
let thisUser = ref.childByAppendingPath(auth.uid)

ref.observeSingleEventOfType(.Value, withBlock: { snapshot

  //read the user data from the snapshot and do whatever with it
  let name = snapshot.value["user_name"] as? String
  print(name!)  

  //now update the status
  thisUsersStatusRef.setValue("In")
  //and the timestamp
}

When the user signs out, all you need to do is the calculation for hours, set current_status to out and write the timestamp

thisUsersStatusRef.setValue("Out")
thisUsersHoursRef.setValue(hours)
thisUsersTimeStamp.setValue(timestamp)

The big picture here is that if you read in the user data when they log in, you have all of the information up front to calculate hours etc as it can be stored in a variable so when they log out, do the calculation and write. So it's essentially only hitting Firebaes twice (once when they log in and once when they log out).

The last thing is that each user should be observing the /mentors node for childAdded, childChanged or childRemoved events (NOT Value). With those observers, when a user is added, edited or changed, all of the users are notified with that single node of data, and can update their UI accordingly.

By : Jay


This video can help you solving your question :)
By: admin