I'm trying to load in different values for each cell in a tableview. Currently, I load in a teamID, display it on the current cell, then use that ID to load in the other attributes of the team.
self.ref?.child("Teams").child(currentTeamID).child("Number").observeSingleEvent(of: .value, with: { (snapshot) in
let number1 = snapshot.value as? Int
if let teamNum = number1 {
Cell.teamNumber.text = "team " + String(teamNum)
//breakpoint
}
})
self.ref?.child("Teams").child(currentTeamID).child("memberCount").observeSingleEvent(of: .value, with: { (snapshot) in
let memcon = snapshot.value as? Int
if let membercount = memcon {
Cell.userCount.text = "Members: " + String(membercount)
//breakpoint
}
})
return Cell
My issues comes when trying to load in these other attributes. Should I be doing this is a different way? Right now it loads only the second .observeSingleEvent I have tried placing breakpoint where I indicated above, but only the second one ever gets hit. Do I need a separate reference or is there a way to load all the values from a parent object?
Thanks a whole bunch.
Added Firebase Structure:
ftc-scouting-app
Teams
Brophy Robotics
Name: "Brophy Robotics"
Number: "201"
Password: "bronco"
memberCount: 2
memberList
member1: "5ilQc8KlrERLAmtFXjWaOZLIcoC3"
member2: "syV9SS6S9hY8PyKBOC0VQ3NNv0v2"
Users
5ilQc8KlrERLAmtFXjWaOZLIcoC3
(User Info Values)
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
(User Info Values
The values I'm trying to load are the team number and the member count. I want to put them on the cell as it loads in each team that each user has. So, I just need it to load each value and put it on my custom table view cell that has all the fields for it. To clarify - I already know that it retrieves the team ID properly because it is able to put it on the cell.
The value currentTeamID is a value that I have already loaded in, and is the id (which is the same as the name) of the current cell's prospective team.
First, change the structure
ftc-scouting-app
Teams
Jyis9009kos0kslk //should be generated with childByAutoId()
Name: "Brophy Robotics"
Number: "201"
Password: "bronco"
memberCount: "2"
memberList:
5ilQc8KlrERLAmtFXjWaOZLIcoC3: true //uid as the key
syV9SS6S9hY8PyKBOC0VQ3NNv0v2: true
Users
5ilQc8KlrERLAmtFXjWaOZLIcoC3
(User Info Values)
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
(User Info Values)
Then, let's retrieve just the one team node and get some data
let teamsRef = self.ref.child("ftc-scouting-app").child("Teams")
let thisTeamRef = teamsRef.child("Jyis9009kos0kslk")
thisTeamRef.observeSingleEvent(of: .value, with: { snapshot in
let teamDict = snapshot.value as! [String: AnyObject]
let teamName = teamDict["Name"] as! String
print(teamName)
let memCount = teamDict["memberCount"] as! String
print(memCount)
let memberList = teamDict["memberList"] as! [String: AnyObject]
for user in memberList {
print(user.key)
}
})
and the output is
Brophy Robotics
2
5ilQc8KlrERLAmtFXjWaOZLIcoC3
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
each event events asynchronously. you should use completion block in your each event.
func getNumber (completion: #escaping (String)->()){self.ref?.child("Teams").child(currentTeamID).child("Number").observeSingleEvent(of: .value, with: { (snapshot) in
let number1 = snapshot.value as? Int
if let teamNum = number1 {
completion(String(teamNum))
}
})}
getNumber(completion: {(teamNum) in
self.ref?.child("Teams").child(currentTeamID).child("memberCount").observeSingleEvent(of: .value, with: { (snapshot) in
let memcon = snapshot.value as? Int
if let membercount = memcon {
Cell.teamNumber.text = "team " + teamNum
Cell.userCount.text = "Members: " + String(membercount)
//breakpoint
}
})
})
Related
I have 2 records in my users table
This code below
let fcmTokenRef = Database.database().reference().root.child("users").child(id!).child("fcmToken")
fcmTokenRef.observe(DataEventType.value, with: { (snapshot) in
print(">>",snapshot)
})
will print out the token of a child
How do I adjust my code to print all the tokens for all my children?
You can try
let fcmTokenRef = Database.database().reference().root.child("users").observe(DataEventType.value, with: { (snapshot) in
print(">>",snapshot)
let dic = snapshot.value as! [String:[String:Any]]
Array(dic.values).forEach {
let str = $0["fcmToken"] as! String
print(str)
}
})
You’re requesting a onetime read, hence you’re reading the data once. You need to use .childAdded
Try this:
let fcmTokenRef = Database.database().reference().child(“users”)
fcmTokenRef.observe(.childAdded, with: { (snapshot) in
print(">>",snapshot)
guard let data = snapshot as? NSDictionary else {return}
var each_token = data[“fcmToken”] as? String
print(“all tokens: \(each_token!)”)
})
#puf says something very important:
differences between child added and value firebase
The child_added event fires for each matching child under the node that you query. If there are no matching children, it will not fire.
I have an app where I have a list of data that I manually enter into Firebase. Here is an image of the Firebase Database:
When the user selects an index row, it goes into another view controller. Inside that view controller there is a tableview, which I want to populate with the data from the “name” that I selected.
For example, if I select the “name” from [acct0], I want the “name” and “genre” to also appear in the new tableview.
Currently, I am trying to print the data based on the selection in the previous view controller.
let user = User()
ref?.child("FeaturedAccounts/AccountNames").child(user.name!).observeSingleEvent(of: .value, with: { (snapshot) in
let userDict = snapshot.value as! [String: Any]
let genre = userDict["genre"] as! String
print("Genre: \(genre)")
print(user.name!)
})
Your query looks correct to me. It's hard to say exactly what is going wrong without more information. Here is some code you can use to help debug your issue. If you post some more of your code or explain exactly what is currently happening with your query I can help more.
let user = User()
let ref = Database.database().reference()
ref.child("FeaturedAccounts").child("AccountNames").child(user.name!).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print(snapshot.value)
}
else {
print("Query returned no data")
}
if let userDict = snapshot.value as? [String: AnyObject] {
if let genre = userDict["genre"] as? String {
print("Genre: \(genre)")
}
else {
print("could not cast genre to string")
}
}
else {
print("Could not cast userDict")
}
})
My data structure is something like the following:
restaurant_owners
|
|owner_id (a unique ID)
|
|restaurant_name
|email
restaurant_menus
|
|restaurant_name
|
|dish_type (drinks, appetizer, etc...)
|
|dish_id (a unique ID)
|
|name
|
|price
The idea of the app is basically to allow "restaurant_owners" to login and manage the menu of their respective restaurant. However I am having problems with the following code: (note that the fetchDish function is called in viewDidLoad)
func fetchDish() {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
//first time referencing database
FIRDatabase.database().reference().child("owners").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
DispatchQueue.main.async{
restaurantName = dictionary["name"] as? String
print(restaurantName!)
}
}
})
//second time referencing database
FIRDatabase.database().reference().child("restaurants").child(restaurantName!).child("appetizer").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}, withCancel: nil)
}
What I am trying to do is to retrieve the the name of the restaurant for the current logged in user and store it in the variable "restaurantName". Then when I am referencing the database for the second time I can use this variable inside of .child (e.g.: .child(restaurantName)).
However, when I run this, I get an error saying that the restaurantName (in the database reference) is of value nil. I tried putting in some breakpoints and it seems like the first line of the second database reference is operated before whatever is "within" the first database reference, so basically restaurantName is called before any value is stored in it.
Why is this occurring? How do I work around this problem? Also, what are the best practices to achieve this if I'm doing it completely wrong?
NoSQL is very new to me and I have completely no idea how I should design my data structure. Thanks for the help in advance and please let me know if you need any other information.
UPDATE:
The problem was solved by changing my data structure to what Jay has suggested. The following code is what worked for me: (modified Jay's code a bit)
func fetchOwner() {
let uid = FIRAuth.auth()?.currentUser?.uid
let ownersRef = FIRDatabase.database().reference().child("owners")
ownersRef.child(uid!).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantID = dict["restaurantID"] as! String
self.fetchRestaurant(restaurantID: restaurantID)
}
}, withCancel: nil)
}
func fetchRestaurant(restaurantID: String) {
let restaurantsRef = FIRDatabase.database().reference().child("restaurants")
restaurantsRef.child(restaurantID).child("menu").observe(.childAdded, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
A couple of things:
Firebase is Asynchronous and you have to account for that in your code. As it is in the post, the second Firebase function may execute before the first Firebase function has successfully returned data i.e. restaurantName may be nil when the second call happens.
You should nest your calls (in this use case) to ensure data is valid before working with it. Like this.. and keep reading
let ownersRef = rootRef.child("owners")
let restaurantRef = rootRef.child("restaurants")
func viewDidLoad() {
fetchOwner("owner uid")
}
func fetchOwner(ownerUid: String) {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
ownserRef.child(ownerUid).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
restaurantId = dict["restaurant_id"] as? String
fetchRestaurant(restaurantId)
}
}
})
}
func fetchRestaurant(restaurantId: String) {
restaurantRef.child(restaurantId).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantName = dict["name"] as! String
let menuDict = dict["menu"] as! [String:Any]
self.dataSourceArray.append(menuDict)
menuTableView.reloadData()
}
}
}
Most importantly, it's almost always best practice to disassociate your key names from the data it contains. In this case, you're using the restaurant name as the key. What if the restaurant name changes or is updated? You can't change a key! The only option is to delete it and re-write it.... and... every node in the database that refers to it.
A better options it to leverage childByAutoId and let Firebase name the nodes for you and keep a child that has the relevant data.
restaurants
-Yii9sjs9s9k9ksd
name: "Bobs Big Burger Barn"
owner: -Y88jsjjdooijisad
menu:
-y8u8jh8jajsd
name: "Belly Buster Burger"
type: "burger"
price: "$1M"
-j8u89joskoko
name: "Black and Blue Burger"
type: "burger"
price: "$9.95"
As you can see, I leveraged childByAutoId to create the key for this restaurant, as well as the items on the menu. I also referenced the owner's uid in the owner node.
In this case, If the Belly Buster Burger changes to the Waist Slimming Burger, we can make one change and it's done and anything that references it is also updated. Same thing with the owner, if the owner changes, then just change the owner uid.
If the restaurant name changes to Tony's Taco Tavern, just change the child node and it's done.
Hope that helps!
edit: Answer to a comment:
To get the string (i.e. the 'key' of a key:value pair) immediately created by .childByAutoId()
let testRef = ref.child("test").childByAutoId()
let key = testRef.key
print(key)
func generateDataForRecents() {
if URLArrayStringThisSeason.count == 0 {
self.activityIndicator2.isHidden = false
self.activityIndicator2.startAnimating()
}
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("palettes").queryLimited(toFirst: 100).observe(.value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict as [String:AnyObject]{
let URL = each.value["URL"] as! String
self.URLArrayStringRecents.append(URL)
//print(self.URLArrayString.count)
//print(snapshot)
//let pictureTitle = each.value["title"] as! String
print(self.URLArrayStringRecents.count)
}
}
self.whatsNewCollectionView?.reloadData() //Reloads data after the number and all the URLs are fetched
self.activityIndicator2.stopAnimating()
self.activityIndicator2.isHidden = true
})
}
The following code does a retrieval of data each time the function is called, or when a new data is added.
This is extremely useful when the app is first started up or closed and then restarted. However, when the app is running, whenever a new entry is added, the code seemed to run again and thus appending twice the amount of new data.
For example, when there are already 15 entries identified and then suddenly a new entry is added, the array of the URL would contain 15+16 thus amounting to a total of 31.
How do I make it such that the new data is added to the array instead of adding the entire snapshot in?
You do that by listening for .childAdded events, instead of listening for .value:
var query = databaseRef.child("palettes").queryLimited(toFirst: 100)
query.observe(.childAdded, with: { (snapshot) in
let URL = snapshot.childSnapshot(forPath/: "URL").value as! String
self.URLArrayStringRecents.append(URL)
}
Since you have a limit-query, adding a 101st item means that one item will be removed from the view. So you'll want to handle .childRemoved too:
query.observe(.childRemoved, with: { (snapshot) in
// TODO: remove the item from snapshot.key from the araay
})
I recommend that you spend some time in the relevant documentation on handling child events before continuing.
Please check below method. I have use this method not getting any duplicate entry.
func getallNotes()
{
let firebaseNotesString: String = Firebase_notes.URL
let firebaseNotes = FIRDatabase.database().referenceFromURL(firebaseNotesString).child(email)
firebaseNotes.observeEventType(.Value, withBlock: { snapshot in
if snapshot.childSnapshotForPath("Category").hasChildren()
{
let child = snapshot.children
self.arrNotes = NSMutableArray()
self.arrDictKeys = NSMutableArray()
for itemsz in child
{
let childz = itemsz as! FIRDataSnapshot
let AcqChildKey : String = childz.key
if AcqChildKey == AcqIdGlobal
{
if (childz.hasChildren() == true)
{
let dictChild = childz.value as! NSMutableDictionary
self.arrDictKeys = NSMutableArray(array: dictChild.allKeys)
for i in 0..<self.arrDictKeys.count
{
let _key = self.arrDictKeys.objectAtIndex(i).description()
print(_key)
let dictData : NSMutableDictionary = NSMutableDictionary(dictionary: (dictChild.valueForKey(_key)?.mutableCopy())! as! [NSObject : AnyObject])
dictData.setObject(_key, forKey: "notesId")
self.arrNotes.addObject(dictData)
}
}
}
}
self.tableviewNote.reloadData()
}
})
}
As for the query for removed child,
query.observe(.childRemoved, with: { (snapshot) in
print(snapshot)
let URL = snapshot.childSnapshot(forPath: "URL").value as! String
self.URLArrayStringThisSeason = self.URLArrayStringThisSeason.filter() {$0 != URL}
self.thisSeasonCollectionView.reloadData()
})
it will obtain the URL of the removed child and then update the array accordingly.
I'm building an app using Firebase that displays a list of Questions in a table view. Each table view cell holds the text of the question, the name of the user that posted it, and the profile photo of the user that posted it.
Here is how I've structured my JSON tree:
"questions"
"firebase_autoID_01"
"name": "Jim"
"text": "Why is the earth round?"
"userID": "123456"
"userInfo"
"userID": "123456"
"photoURL": "gs://default_photo"
I'm trying to retrieve the name and text for each question and the photoURL of the user with the corresponding userID to then place those 3 elements in each table view cell. So I'm trying to retrieve data from the questions child node AND the separate userInfo child node to put that data into the same table view cell. Here is the code I've been trying to do that with:
When a user first gets created, I set their photoURL to a default image that I manually uploaded to Firebase Storage:
...
let placeholderPhotoRef = storageRef.child("Profile_avatar_placeholder_large.png")
let placeholderPhotoRefString = "gs://babble-8b668.appspot.com/" + placeholderPhotoRef.fullPath
//let placeholderPhotoRefURL = NSURL(string: placeholderPhotoRefString)
let data = [Constants.UserInfoFields.photoUrl: placeholderPhotoRefString]
self.createUserInfo(data)
}
func createUserInfo(data: [String: String]) {
configureDatabase()
let userInfoData = data
if let currentUserUID = FIRAuth.auth()?.currentUser?.uid{
self.ref.child("userInfo").child(currentUserUID).setValue(userInfoData)
}
}
Issues start occurring when I try to retrieve data from both the questions and userInfo:
var photoUrlArray = [String]()
func configureDatabase() {
ref = FIRDatabase.database().reference()
_refHandle = self.ref.child("questions").observeEventType(.ChildAdded, withBlock: {(snapshot) -> Void in
self.questionsArray.append(snapshot)
//unpack the userID from the "questions" child node to indicate which user to get the photoURL from in the "userInfo" child node
if let uid = snapshot.value?[Constants.QuestionFields.userUID] as? String {
self._photoURLrefHandle = self.ref.child("userInfo").child(uid).observeEventType(.ChildAdded, withBlock: {(snapshot) -> Void in
if let photoURL = snapshot.value as? String {
self.photoUrlArray.append(photoURL)
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.questionsArray.count-1, inSection: 0)], withRowAnimation: .Automatic)
}
})
}
})
}
I get the following error in the Xcode console:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'"
What I'm doing here is creating a firebase handle to observe/retrieve the users photoURL from the userInfo child, and that handle is created inside the closure of the handle that observes/retrieves the data from the questions child.
Questions: If this is not the right way to retrieve the data I want, how should I approach this? And how should I structure my JSON data if it isn't currently structure the right way?
Here is how I'm unpacking the name and text from the questions child node in the table view and it's working fine:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell! = self.tableView.dequeueReusableCellWithIdentifier("tableViewCell", forIndexPath: indexPath)
//unpack question from database
let questionSnapshot: FIRDataSnapshot! = self.questionsArray[indexPath.row]
var question = questionSnapshot.value as! Dictionary<String, String>
let name = question[Constants.QuestionFields.name] as String!
let text = question[Constants.QuestionFields.text] as String!
cell!.textLabel?.text = name + ": " + text
return cell!
}
Question: should I unpack the photoURL here or in the previous configureDatabase() function?
Since you asked for suggestion's on how to store your database structure in JSON. :-
Suggested JSON Structure : -
{"Users" : {
"userID_1" : {
userName : "Jim",
userImageURL : "gs://default_photo",
questionsAsked : {
"questionID_1" : "QUESTION TEXT",
"questionID_2" : "QUESTION TEXT"
}
}
"userID_2" : {
userName : "Moriarty",
userImageURL : "gs://default_photo",
questionsAsked : {
"questionID_1" : "QUESTION TEXT",
"questionID_2" : "QUESTION TEXT"
}
}
}
}
Since you are aiming to display the image and name of the user who posted the question with the question itself , i think it would be best if you created the child of questionAsked in the usersId itself that way you can keep track of the no and what questions were asked by that particular user.
Saving the user's profile data would go like this : -
FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser()!.uid).setValue([
userName : "Jim",
userImageUrl : //URL that you want to save
])
//Instead of FIRAuth.auth()!.currentUser()!.uid you can use childByAutoId too but i wont recommend that.
When user makes a post call a function with this code : -
let rootRef = FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser()!.uid).child("questionsAsked")
rootRef.observeEventType(.Value, withBlock: {(snap) in
if snap.exists(){
if let questionsDictionary = snap.value as? NSMutableDictionary{
questionsDictionary.setObject("Why is the earth round?", forKey: //your questionID)
rootRef.setValue(questionsDictionary)
}
}else{
rootRef.setValue([
your_questionID : "Why is the earth round?"
])
}
})
How to retrieve a question :-
let parentRef = FIRDatabase.database().reference().child("Users")
parentRef.observeEventType(.Value, withBlock: {(userSnap) in
if userSnap.exists(){
for eachUser in userSnap.value as! [String:AnyObject]{
let userId = eachUser.key as! String
parentRef.child(userId).observeEventType(.Value, withBlock: {(userSnap) in
if let userInfo = userSnap.value as? [String:AnyObject]{
let userNameRecieved = userInfo["userName"] as! String
let userImageUrlRecieved = userInfo["userImageURL"] as! String
parentRef.child(userId).child("questionsAsked").observeEventType(.Value, withBlock: {(usersQuestion) in
if userQuestion.exists(){
for eachQ in userQuestion.value as! [String:AnyObject]{
let question = eachQ.value as! String
//Create a function named - "initialiseCell" , that will initialise the datasource of the tableView with username, photoURL and question as its : String parameters
initialiseCell(question,userNameRecieved,userImageUrlRecieved)
}
}
})
}
})
}
}
})
As for yourQuestion_ID : -
let yourQuestionID = "\(Int(NSDate.timeIntervalSinceReferenceDate() * 1000))
This will produce an unique yourQuestionID every next second or maybe millisecond
This retrieve function in this stage, calls for every question in your entire apps database but you can filter it by modifying the code further...
This is how I ended up structuring my Firebase JSON tree:
"users":
"userID4321":
"displayName": "bob"
"photoURL": "gs://default_photo"
"questions":
"questionID5678":
"text": "How are you?"
"userID": "userID4321"
I then implemented the following code in my configureDatabase method:
func configureDatabase() {
ref = FIRDatabase.database().reference()
_refHandle = self.ref.child("questions").observeEventType(.ChildAdded, withBlock: {[weak self] (questionSnapshot) in
let questionID = questionSnapshot.key
var question = questionSnapshot.value as! [String: AnyObject]
question[Constants.QuestionFields.questionID] = questionID
let userID = question[Constants.QuestionFields.userID] as! String
let usersRef = self?.ref.child("users")
usersRef?.child(userID).observeEventType(.Value, withBlock: { (userSnapshot) in
var user = userSnapshot.value as! [String: AnyObject]
let photoURL = user[Constants.UserFields.photoUrl] as! String
let displayName = user[Constants.UserFields.displayName] as! String
question[Constants.QuestionFields.photoUrl] = photoURL
question[Constants.QuestionFields.displayName] = displayName
...
The observeEventType handle on the questions child gives me a snapshot of each question object every time a new child gets added or a new question gets created.
I then create a local dictionary from that question snapshot that was retrieved from the Firebase server.
With the userID from that question, I can then identify the corresponding user with another observeEventType method and retrieve that user's photoURL and append it to the question snapshot.
Once all the data I need is in the local question dictionary, I append it to my questionsArray which gets sent to the table view to display each question with the appropriate data.
Let me know if anyone who reads this would have done it differently!