Swift Firebase Completion Block Infinite Loop - ios

I am making an app that holds many books in firebase. I am getting a very strange problem wherein my application will infinite loop when adding a new book and keep adding the same book as fast as it can. If there would be any way that a more experienced person could take a look, I would be very grateful.
#IBAction func userHasBook(sender: AnyObject) { // Called after filling a new book form
let email = FIRAuth.auth()?.currentUser?.email!
let school = email!.substringWithRange(Range(email!.characters.indexOf("#")!.advancedBy(1) ..< email!.characters.indexOf(".")!)) // for db organization
//A few lines here that ensure that the fields are filled correctly (clutter so i didn't add them)
ref.child(school).observeEventType(.Value, withBlock: { (snapshot) in
self.bookIndex = snapshot.value!["numSelling"] as! Int
self.addSellingBook(); // we now know it is done finding the value, right?
}) { (error) in
print(error.localizedDescription)
}
}
func addSellingBook(){
let bookRef = self.ref.child(school).child("selling").child(Int(self.bookIndex).description)
let book : [NSObject : AnyObject] = ["uid": (FIRAuth.auth()?.currentUser?.uid)!,
"title": self.titleField.text!,
"authors": self.authorsField.text!,
"edition": self.editionField.text!,
"price": self.priceField.text!,
"isbn" : self.isbn] // this is the data that is added infinitely many times
bookRef.updateChildValues(book, withCompletionBlock: { (NSError, FIRDatabaseReference) in //update the book in the db
let newIndex = self.bookIndex + 1
self.ref.child(self.school).child("numSelling").setValue(newIndex, withCompletionBlock: { (NSError, FIRDatabaseReference) in // after that update the index
self.performSegueWithIdentifier("backToMain", sender: nil) // and after that go back to main
})
})
Thanks a ton and ask me if you need anything more!
EDIT: JSON BEFORE BELOW
{
"colorado" : {
"numBuying" : 0,
"numSelling" : 0,
"users" : {
"2nU0jp4ITjgQ6ElSQWc7t5qj62t1" : {
"email" : "vhegde#colorado.edu"
}
}
},
"creek" : {
"numBuying" : 0,
"numSelling" : 2,
"selling" : [ {
"authors" : "A. S. A. Harrison",
"edition" : "Only Edition",
"isbn" : "1101608064",
"price" : "5.00",
"title" : "The Silent Wife",
"uid" : "eJvdVx3J8EYZPH3mlbYLBcPDkD12"
}, {
"authors" : "Jamie McGuire",
"edition" : "Only Edition",
"isbn" : "1476712050",
"price" : "5.00",
"title" : "Beautiful Disaster",
"uid" : "eJvdVx3J8EYZPH3mlbYLBcPDkD12"
} ],
"users" : {
"eJvdVx3J8EYZPH3mlbYLBcPDkD12" : {
"email" : "vhegde#creek.edu"
}
}
}
}
Then, I add another book (index of 2) and rather it keeps adding infinite books and infinitely increments index (numSelling). I don't want to post that JSON as it is like 300 lines long.

figured it out, instead of using observeEventType, you have to use observeSingleEventOfType

Please change Your code as i have implemented in below method to set online/offline.
// MARK: - User Online/Offline
func setUserOnlineOffline(userId: String!, isOnline: Bool!) {
Let ref = FIRDatabase.database().reference().child(FIRPATH_USER_USERID_DETAILS(userId))
if isOnline == true {
ref.updateChildValues(["last_seen": "Online"])
} else {
ref.updateChildValues(["last_seen": FIRServerValue.timestamp()])
}
ref.onDisconnectUpdateChildValues(["last_seen": FIRServerValue.timestamp()])
}
//note: here you have to set string, so please replace dictionary of nsobject with string .. might be helpful to you

Related

How to fetch nested data from Firebase realtime database by implementing observer in swift

Firebase data-structure
lastLocations ( batteryStatus, lat, long, timestamp, uid)
profiles (name, phoneNumber, picture, uid)
userFriends ( basis on the uid -> how many friends -> conversationUid, friendStatus, notify, phoneNumber, uid)
My Code:
I have already created tableview and xib for it.
I have created model for last location, profiles, userFriends.
I already fetched the friend list but on Observe .ChildAdded
My uid zzV6DQSXUyUkPHgENDbZ9EjXVBj2
Link: https://drive.google.com/file/d/19cnkY03MXjrTFgzzPCdvmOuosDRvoMx9/view?usp=sharing
Issues:
Not getting an idea how to fetch location & profile with friend list efficient way with observer so any change come it reflects. Firebase is asyncrohonous process.
observer implementation so data not completely load every-time
Results to achieve:
I need to show friends list ( name, profile picture, battery status, lat long(address), timeStamp ) on tableview on the basis of my uid.
Firebase JSON
{
"lastLocations": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"batteryStatus": 22,
"latitude": 40.9910537,
"longitude": 29.020425,
"timeStamp": 1556568633477,
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
},
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"batteryStatus": 88,
"latitude": 41.0173995,
"longitude": 29.1406086,
"timeStamp": 1571778174360,
"uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
}
},
"profiles": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
},
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"fcmToken": "enMneewiGgg:APA91bHyA4HypWUYhxGTUTTch8ZJ_6UUWhEIXRokmR-Y-MalwnrtV_zMsJ9p-sU_ZT4pVIvkmtJaCo7LFJYJ9ggfhc1f2HLcN9AoIevEBUqyoMN-HDzkweiUxAbyc84XSQPx7RZ1Xv",
"name": "Murad",
"phoneNumber": "+915377588674",
"picture": "profile/zzV6DQSXUyUkPHgENDbZ9EjXVBj2/a995c7f3-720f-45bf-ac58-b2df934e3dff.jpeg",
"uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
}
},
"userFriends": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915377588674",
"uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
}
},
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
}
}
}
}
Swift Function:
func getFrndDataList(){
AppData.removeAll()
ref.child("userFriends").child("zzV6DQSXUyUkPHgENDbZ9EjXVBj2").observe(.childAdded, with: { (snapshot) in
guard let data = try? JSONSerialization.data(withJSONObject: snapshot.value as Any) else { return }
let frndList = try? JSONDecoder().decode(Friend.self, from: data)
self.AppData.append(frndList!)
self.tableView.reloadData()
print([frndList])
})
}
Note: After writing this answer I realized it was way long but this is a big question and there are a lot of elements to address.
My first suggestion is to change the structure as it's overly complicated for what's being done with the data. Also, there is repetitive data that's not needed so that should be changed as well. For example, here's your profiles node
"profiles": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1" <- remove this, not needed.
},
As you can see, each child node has a key of the user id. But, you are also storing the user id as a child node as well. They key is the uid and will always be available so no need for duplication there and the child node should be removed.
Based on comments, this is a much better structure
/users
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1
"batteryStatus": 22,
"latitude": 40.9910537,
"longitude": 29.020425,
"timeStamp": 1556568633477,
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915377588674",
and then, to keep track of a users friends, it becomes this
/userFriends
zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: true //their friend
IRoo0lbhaihioSSuFETngEEFEeoi: true //another friend
To load this users friends, we read the data at /userFriends/this_users_id and then iterate over the child nodes loading the data for display in the tableView
Lets start with an object that will be used to hold each friends data, and then an array that will be used as a tableView Datasource
class FriendClass {
var uid = ""
var name = ""
//var profilePic
//var batteryStatus
init(withSnapshot: DataSnapshot) {
self.uid = withSnapshot.key
self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
}
}
var myFriendsDataSource = [FriendClass]()
Then a functions to read the users node, iterate over the users friends uid's and read in each users data, populating the FriendClass object and storing each in an array. Note that self.ref points to my firebase.
func loadUsersFriends() {
let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
let myFriendsRef = self.ref.child("userFriends").child(uid)
myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
let uidArray = snapshot.children.allObjects as! [DataSnapshot]
for friendsUid in uidArray {
self.loadFriend(withUid: friendsUid.key)
}
})
}
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
})
}
Now that we have the code to read in the data, you also want to watch for changes. There are a number of options but here's two.
1) I'll call this brute force.
Simply attach a .childChanged observer to the /users node and if something changes, that changed node is passed to the observer. If the key to that node matches a key in myFriendsDataSource array, update that user in the array. If no match, then ignore it.
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("users")
usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.name), updating")
//friend(updateWithSnapshot: snapshot) //leave this for you to code
}
})
}
2) Selective observing
For this, we simply attach an .childChanged observer to each friend node - and that can be done within the code example from above
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
//add an observer to this friends node here.
})
}
One last thing: I didn't address this
"friendStatus": "STATUS_ACCEPTED",
I would think that only friends you accepted are in the friends list so the use is a tad unclear. However, if you want to use it you could do this
/userFriends
zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: "STATUS_ACCEPTED"
IRoo0lbhaihioSSuFETngEEFEeoi: "STATUS_DECLINED"
and then as you're itering over friends to load, ignore the ones that are declined.
If you MUST keep your current structure (which I do NOT recommend) the techniques in this answer will work for that structure as well, however, it will be a lot more code and you're going to be moving around a lot of unneeded extra data so the Firebase bill will be higher.

Listing Firebase data and relationships

I have just started working with Firebase database and I am a bit confused how to structure the database. In the following example I have a users object and a groups object. Each user can be part of multiple groups and every group can have multiple users. The proposed database structure is the following, according to "Structure Your Database".
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers": true,
"womentechmakers": true
}
}
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"startDate": "24-04-1820",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
}
}
}
Let's say I want to display all groups in a list in my app, with the group name and start date. How would I make that database call? Since the user object only contains the id of the groups, would I then have to make a separate call to the database for every group just to find out the name and start date of the group? If there are many groups in the list, then that becomes a lot of calls. My group might contain a lot of other information as well so this doesn't seem good for performance. Can I get all the groups in the groups list of the user, in one call?
One though I had was to include the name and start date in the groups object under the user:
"users": {
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers":{
"name": "Historical Tech Pioneers",
"startDate": "24-04-1820"
},
"womentechmakers":{
"name": "Women in Technology",
"startDate": "13-10-1823"
}
}
}
}
}
but this solution seems to add a lot of duplicate data. Also if I want to update the name I would have to do that in multiple locations. And maybe I want to add a sponsor organization object, that also contains group, and then want to list them. Then there would be 3 places to update the information on. How would I solve this?
You would then have two possibilities, one would be to store the data you need (duplicating it) in the groups node of each user.
The other, which is the one that I would recommend the most, would be to add an observeSingleEvent(of: .value) inside your first observer (that could be an observe(.value), observe(.childAdded) or whatever).
Say you have an array of all your group members, and an object named AppUser that represents a user in your app :
var groupMembers = [AppUser]()
To detect whenever a new member is added to a group for example, you could use a .childAdded observer for example :
func groupWasAddedObserver(completion: #escaping () -> ()) {
// Add a .childAdded observer to the group's members node (groupId should be defined somewhere) :
groupsRef.child(groupId).child("members").observe(.childAdded, with: { [weak self] (snapshot) in
// When you get the snapshot, store its key, which represents the member id :
let memberId = snapshot.key
// fetch this member's profile information using its id :
self?.getUser(memberId, completion: { (groupMember) in
// Once you got the profile information of this member, add it to an array of all your members for example :
self?.groupMembers.append(groupMember)
// Call the completion handler so that you can update the UI or reload a table view somewhere maybe depending on your needs :
completion()
})
})
}
And the second method to fetch a user data knowing his or her id :
func getUser(_ userId: String, completion: #escaping (AppUser) -> ()) {
// Add the observerSingleEvent observer :
usersRef.child(userId).observeSingleEvent(of: .value, with: { (snapshot) in
// Get the data you need using the snapshot you get :
guard let email = snapshot.childSnapshot(forPath: "email").value as? String else { return }
guard let name = snapshot.childSnapshot(forPath: "name").value as? String else { return }
guard let picUrl = snapshot.childSnapshot(forPath: "picUrl").value as? String else { return }
// Call the completion handler to return your user/member :
completion(AppUser(id: snapshot.key, email: email, name: name, picUrl: picUrl))
})
}
As you can see you get the memberId of each user using the snapshot key, and you use this memberId to fetch this specific user data.

How to get user's feed from Firebase?

I have an iOS app that has a feed system basically like Facebook (With Firebase as server) and I'm trying to get the latest Posts from all the people that the user is following, but I can't find a way to loop through all data. Can I get a hand?
So this is more or less how my database is:
{
"Posts" : {
"Zlv1rX3p74R4G8DUvvBOcCiThcx1" : {
"-KacGJ-ps8FgCYvSag-D" : {
"latitud" : "37.33233141",
"longitud" : "-122.0312186",
"text" : "Party at my house",
"timestamp" : "1484586828.01879",
"ubicacionN" : "Apple Inc."
}
}
},
"following" : {
"Zlv1rX3p74R4G8DUvvBOcCiThcx1" : {
"Bdxaooy1HieFajZfZL8OeDFS1Q73" : {
"Uid" : "Bdxaooy1HieFajZfZL8OeDFS1Q73",
"handle" : "dnla",
"name" : "Daniela",
"profile_pic" : "https://firebasestorage.googleapis..."
}
}
}
}
I got the posts in one part and the list of following in another part (I'll add a complete pic of my DB at the end)
I'm not really familiar with NSDictionary so that's why I'm running into troubles, so I started doing this:
self.databaseRef.child("following").child(self.loggedInUser!.uid).observe(.childAdded, with: { (snapshot) in
//Getting the list of following
let snapshot = snapshot.value as? NSDictionary
self.following.append(snapshot)
}) { (error) in
print(error.localizedDescription)
}
for item in self.following {
Some code to get every user that the main user is following latests posts
}
but I'm hitting my head right now, some help please.
Picture explanation: Jalas = Posts

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

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.)
Your problem is the way you are trying to access the data of your Firebase reference. valueForKey isn't the right way to do it. You need to do one of two things:
First cast your firebaseSnap.value to a dictionary, and then use dict[key] syntax.
Snapshot.childSnapshotForPath("current_status").value
I would recommend number 2, as the cleanest way to do it. But if you need to access the reference a lot, you may want to cast to dictionary because accessing it will look nicer that way.
NOTE: Both of these require a firebase event listener to get the firebase snapshot.
Let me know if you have any questions, and good luck!
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.

Kinvey: Object not saving due to error: Cannot map 'X', a non-existant property

I'm part of the group moving from Parse and checking out Kinvey. However, I can't get objects to save. I get the error:
error : Error Domain=KCSAppDataErrorDomain Code=60102 "Entity does not have property 'NumberOfPlayers' as specified in hostToKinveyPropertyMapping" UserInfo={NSLocalizedRecoverySuggestion=Check the hostToKinveyPropertyMapping for typos and errors., NSLocalizedDescription=Entity does not have property 'NumberOfPlayers' as specified in hostToKinveyPropertyMapping, NSLocalizedFailureReason=Cannot map 'NumberOfPlayers', a non-existant property}
I have followed the guide on their website here:
http://devcenter.kinvey.com/ios/guides/datastore#create
My Code:
class Event: NSObject {
var objectId:String?
var Sport:Int?
var NumberOfPlayers:Int?
var SkillLevel:Int?
var Date:NSDate?
var Time:NSDate?
var Competitive:NSNumber?
override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
return [
"objectId" : KCSEntityKeyId,
"Sport" : "Sport",
"NumberOfPlayers" : "NumberOfPlayers",
"SkillLevel" : "SkillLevel",
"Date" : "Date",
"Time" : "Time",
"Competitive" : "Competitive",
]
}
}
And the saving:
let event = Event()
event.Sport = 0
event.NumberOfPlayers = playerQuantity
event.SkillLevel = skillLevel
event.Date = date
event.Time = time
event.Competitive = competitive
let collection = KCSCollection(fromString: "Event", ofClass: Event.self)
let store = KCSAppdataStore(collection: collection, options: nil)
// let store = KCSAppdataStore.storeWithOptions([
// KCSStoreKeyCollectionName : "Event",
// KCSStoreKeyCollectionTemplateClass : Event.self
// ])
store.saveObject(event, withCompletionBlock: { (objects, error) -> Void in
if error != nil { print("error : \(error)"); return }
print("saved objects")
}, withProgressBlock: nil)
Neither way of creating the DataStore has worked. They both return the same error.
I have also checked that the AppId and AppSecret are both correct. I can sign up Users and login ok, I just can't save data.
Any ideas?
Any number types should be classified as NSNumbers instead of Ints

Resources