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")
}
})
Related
This is my first screenshot database in firebase:
This is my second screenshot database in firebase:
I am able to retrieve data from firebase also set in table view. But I want all data that which first screenshot database key (1album) equal to second database post_key: 1album.
I tried like this:
refhandel = ref.child("SongPlay").child("1album"] ).queryOrdered(byChild:"post_key").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? [String: AnyObject]{
let music = MusicDataModel()
music.setValuesForKeys(item)
MusicData.append(music)
}
})
after read firebase documentation, I got my Answer where i made mistake.
here what i was done. i just replace queryEqual instance of child
refhandel = ref.child("SongPlay").queryOrdered(byChild:"post_key").queryEqual(toValue: post_key[myIndex]).observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? [String: AnyObject]{
let music = MusicDataModel()
music.setValuesForKeys(item)
MusicData.append(music)
}
})
So I am creating a forum inside my app and when fetching the topics on the database I also need to fetch some user info like picture and username of the original poster to put it on the table view cells.
To keep the recommendation of keeping the Firebase database flat I have the messages on a separate ref searchable by key and NOT as a child of topics.
I can't keep something like opImage and opUsername as childs of topics because if a user changes its username or profile image I would need to change it on every topic he ever participated as well.
What is the best way to handle this?
The method to fetch from Firebase would look something like this. The problem with that implementation is that the Firebase calls are asynchronous and there would be no guarantee that the image would be attached to the correct topic.
DataService.ds.Topics_Base.child(cat.key!).observeSingleEvent(of:.value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {//snapshots = all topics
for top in snapshots{
if let dict = top.value as? Dictionary<String,AnyObject>{
guard let title = dict["title"] as? String,let text = dict["text"] as? String,let time = dict["time"] as? String,let username = dict["username"] as? String,let timeSimple = dict["time-simple"] as? String,let lastPost = dict["last-post"] as? String,let open = dict["open"] as? Bool else{
continue
}
let newTopic = Topic(subject: title, text: text, time: time, topicKey: top.key, username: username)
self.allTopics.append(newTopic)
print(self.allTopics.count)
if let email = dict["email"] as? String{
//FETCH USER INFO FROM EMAIL FETCHED
let validEmail = HelperMethods.removeSpecialCharactersFromEmail(email: email)
DataService.ds.Users_Base.child(validEmail).observeSingleEvent(of: .value, with: {(snapshot) in
if let userDict = snapshot.value as? Dictionary<String,AnyObject>{
if let imgUrl = userDict["profile_image_url"] as? String{
self.allTopics[currentIndex].opImageUrl = imgUrl
}
self.allTopics.append(newTopic)
if (totalTopics == snapshots.count){
DispatchQueue.main.async {
self.allTopics.sort { $0.lastPostTime! > $1.lastPostTime!}
self.tableView.reloadData()
self.refreshControl.endRefreshing()
}
}
}
})
}
} }
}
}
)
}
Thanks in advance.
I ended up deciding to fetch the user info AFTER fetching all topics and then looping through the topics array and attaching user info for each topic.
EXAMPLE:
1 - fetch all topics and create array
2 - for each topic in array{
fetch user on Firebase using a property like topic.email
set something like:
topic.imageUrl = user.imageUrl
topic.username = user.username
}
reload table view
I am a beginner. The main function is that when user click a each button of cell, it will update data. But I just write a new data, it cannot update existing data.
ViewController.swift
This function write's a new key is not save existing key. How can I solve?
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("location").queryOrderedByKey().observe(.childAdded, with: {snapshot in
guard let firebaseResponse = snapshot.value as? [String:Any] else
{
print("Snapshot is nil hence no data returned")
return
}
let status = firebaseResponse["status"] as! Int
let key = databaseRef.childByAutoId().key
let up = ["id":key,
"status":status] as [String : Any]
databaseRef.child("location").child(key).setValue(up)
self.posts.insert(postStruct(status:status,key:key), at: 0)
self.tableView.reloadData()
})
This my Exercise xcode in google drive
https://drive.google.com/open?id=0B2jj6V1tOGcVTm9JSy1uNV9fZnM
This is firebase data for image:
Problem is in this line
let key = databaseRef.childByAutoId().key
You are creating new autoID instead of using the current child autoId from snapshot.key, so change that line with below one and you all set to go.
let key = snapshot.key
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
}
})
})
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)