How to display data from nested firebase node in swift? - ios

The following is my database design in firebase
- Sports
- MainCategory (hypothetical category name)
- SubCategory1
- K_tgbBt6Vx0-kkg7e63
- name:football
- K_tgbBt6Vx0-kkg7a99
- name:golf
- Subcategory2
- K_tgbBt6Vx0-kkgad21
- name:Snooker
- K_tgbBt6Vx0-kkg7e00
- name:Table Tennis
- MainCategory2
- SomeOtherSubCategory1
-K_tgbBt6Vx0-kkg7e00
My Aim: To get all the sports under the sports node and display them category wise(i.e MainCategory and Sub category) in a uicollectionview and allow the user to select sports of his choice.
Eg. display
Main Category1
SubCategory1
Football, Golf
SubCategory2
Table Tennis, Snooker
Main Category2
SomeOtherSubCategory1
Hockey,Tennis
The following approaches only take me one level deeper in the node
rootref.child("sports").observe(.value, with: { (snapshot) in
let mainCategory = snapshot.key
for child in snapshot.children{
print(child)
}})
rootref.child("sports").observe(.childAdded, with: { (snapshot) in
let mainCategory = snapshot.key
let mySnapshot = snapshot.value! as! NSDictionary
}) { (error) in
print(error.localizedDescription)
}
Also if there is any other way i could structure my database, kindly let me know

`rootref.child("sports").observe(.value, with: { (snapshot) in
//This gets your mainCategory names
let mainCategory = snapshot.key
for child in snapshot.children{
print(child)
}})
rootref.child("sports").observe(.childAdded, with: { (snapshot) in
let mainCategory = snapshot.key
let mySnapshot = snapshot.value! as! NSDictionary
}) { (error) in
print(error.localizedDescription)
}
//Now what you need to do is another snapshot to get the sub categories. It would be something like this:
rootref.child("sports").child("Sub-Category").observe(.value, with: { (snaps) in
//Same steps as you did above
})
`

It's cool to know that a man like you with 2.5k reps is asking such a question. I assume you know MVC, of course in the future, you'd want a reusable Service Class to handle Firebase requests and Model Class to handle the data.
Next, you should know the different ways of observing data on Firebase. There is a Single Event and Event of Type. Read the doc: https://firebase.google.com/docs/database/ios/read-and-write
In structuring your database, Firebase has a doc for that too: https://firebase.google.com/docs/database/web/structure-data
Anyway, I made a sample for you, take a look:
http://www.jsoneditoronline.org/?id=5d9e7067883a538746ace7cdd9e81ebb
I made a new structure, which I believe a better structure, of your databse using jsoneditoronline website. Avoid so much nested nodes as much as possible. See my sample, I made necessary new nodes for faster and easier fetching of data. For example, if you're going to view the link above and download the database and upload it to your Firebase Database, you'll see the structure like so:
As you can see, I have here a parent node called subcategories-sportId, which has child nodes of different subcategories and each of that subcategories, we have the ids of the sports.
Another example, if we would like to get all the sports under subcategory2, we won't be using the data inside our sport node. (see below pic) But instead, we will check the data inside the subcategories-sportid node. Get a reference to that node plus add a child node of the specific subcategory string, then fetch all the sportsIds. Enumerate the sportsIDs and lastly fetch the main data of each sports.
But if we would like to get all the subcategories and main categories of the specific sports, we can use the data inside our sport node (see the above pic)

let cat3 = db.child("API Questions").child("Category3").child("Name")
cat3.observe(FIRDataEventType.value, with:
{ (snapshot) in
let eventDataloc = snapshot.value as? [String: AnyObject] ?? [:]
// self.Cat3Qarray.removeAllObjects()
for (_, value) in eventDataloc
{
let studsmodel = FirebaseStructureCustVM.updateQuestionData(Questiondata: value as![String:Any])
self.Cat3Qarray.add(studsmodel)
//print(studsmodel)
}
self.tableview1.reloadData()
//print snapshot here
})

In the below, the "API Questions" is the "root" and the "Category1" is the "child" of that root if we want to get the "Category1" data such as MaxMarks:"20",Q:"General information",Qid:"Question 1" so you can print the snapshot like this === print("the snap of the cat1 = (snapshot)") === you will get all the data which is inside of the Category1
let cat2 = db.child("API Questions").child("Category1")
cat2.observe(FIRDataEventType.value, with:
{ (snapshot) in
let eventDataloc = snapshot.value as? [String: AnyObject] ?? [:]
for (_, value) in eventDataloc
{
let studsmodel = FirebaseStructureCustVM.updateQuestionData(Questiondata: value as![String:Any])
}
print((snapshot))
})

Related

Swift Firebase - getting multiple uid's then adding those uid's to a child

I've been thinking about this but I thought I'd post a question to get some more thinking power behind this or to see if this is even possible. I am grabbing multiple uid's and then want to take these uid's and append them to a child in my database and then add further data to them. Since they are uid's I can't access them separately which would be a easy firebase "update values" call, so how could I take this list of uid's and then add them to a child so they are their own separate children and then add values to them? I am just thinking about how I would set this firebase call to say "add each one of these uid's as its own child".
How I am getting the uid's
func getEmployees() {
let employees = Database.database().reference().child("Businesses").child(self.otherUser?["uid"] as! String).child("registered_employees").observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
let employess = snapshot.childSnapshot(forPath: "uid")
print(employess)
} else {
print("didnt call right values")
}
})
}
sample of data I would add to uid child
let userMessageRef = Database.database().reference().child("user-messages").child(fromID).child(toID)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId!:1])
The code right above ^^^^ I would want the uid's to be in "toID" and then adding the "messageId" to those uid's
I don't know how I could even do each uid separately in the call because of the inability to extract each one and then set the data.
I think I understand so let me try an answer with an example. How we obtain the uid's we want to write is not outlined in the question so let try this:
Suppose we have a users node that stores our users and if they like pizza
users
uid_0 //the Firebase generated uid
name: "Richie"
likes_pizza: true
uid_1
name: "Marion"
likes_pizza: false
uid_2
name: "Fonzi"
likes_pizza: true
uid_3
name: "Howard"
likes_pizza: false
what we want to do it to get the users that like pizza, craft a new node and store each of the uid's as a parent and then a child of their name.
let usersRef = self.ref.child("users")
let pizzaQueryRef = usersRef.queryOrdered(byChild: "likes_pizza").queryEqual(toValue: true)
pizzaQueryRef.observeSingleEvent(of: .value, with: { snapshot in
guard let allUsers = snapshot.children.allObjects as? [DataSnapshot] else {return}
for user in allUsers {
let key = user.key
let name = user.childSnapshot(forPath: "name").value as! String
let pizzaRef = self.ref.child("pizza_lovers")
let aPizzaLoverRefUid = pizzaRef.child(key).child("their_name")
aPizzaLoverRefUid.setValue(name)
}
})
so this code queries for all users that like pizza (which enables us to access their uid's), and then (per the question) append them to a child in the database and then add further data to them
and then want to take these uid's and append them to a child in my
database and then add further data to them
the result is
pizza_lovers
uid_0
their_name: "Richie"
uid_2
their_name: "Fonzi"
Let me know if I misunderstood the question and I will update.

How to get the value of a key in firebase - iOS

so this is an image of my JSON tree:
my JSON TREE
Question:
I wanted to know how can I check if the username, let's say, sean exists in the usernames. I currently have no idea on how to implement this.
What I've tried:
The key of usernames child is "theUsernameOf-userUID", and that causes the problem as userUID is dynamic and different from each user (from firebase auth), therefore I can't use:
.queryOrderedByChild("theUsernameOf-userUID").queryEqual(toValue: self.usernameTextBox.text!)
The key of usernames child can't be static like theUsername as it would only be able to have 1 value / not able to generate more node.
Thank you so much, sorry if I didn't explain clearly enough.
I would like to modify your DB structure as current one is not the correct to perform this query.
It should be like below:
Always use auto incremented keys for queries. Here usernames -> autoGeneratedKey -> yourData (Dictionary - Key-Value pair) Now you can easily check the existence of any key.
let ref = defaultDB.reference.child("usernames")
ref.queryOrdered(byChild: "username").queryEqual(toValue: "sean").observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
print("exists")
}
else {
print("doesn't exist")
}
}
Output: exists
This is the correct way to do so. Just checking for snapshot.exists() will do the job for you.
When you observe any reference in firebase, you get a DataSnapshot in return. The snapshot has a children enumerator property on which you can enumerate each child. Each of the child will be another DataSnapshot. Now, each snapshot has key and value. You want the user name? It's in the value property:
let databaseRef = Database.database().reference(withPath: "usernames")
databaseRef.observe(.value) { (snapshot) in
snapshot.children.forEach({ (child) in
if let child = child as? DataSnapshot, let value = child.value {
print(value) //"Sean", "Yuh"
// here you can check for your desired user
}
})
}
Here is how you can do it
Set the database reference to the usernames node. For example db.ref.child("usernames")
Now parse the snapshot using for loop
let usernames = snapshot.value as! NSDictionary
Now the for loop
for username in usernames{
if username.value == "Sean"{
// do whatever you want here
}
}
You could also:
Make a firebase call to usernames and make that:
let usernames = snapshot.value as? [String: AnyObject],
then all you have to do is something like
if let keyExists = usernames[YOURUSERNAMECHECKSTRING] {
//It's true
}
Just another way of looking at it.
Try This code will Help you you dont need to change your firebase structure
In Swift
Database.database().reference(withPath: "users").queryOrdered(byChild: "usernames").queryEqual(toValue: "yourUserName").observe(.value)
{ (snapshot:DataSnapshot) in
if snapshot.valueInExportFormat() is NSDictionary
{
// User is exits
}
else
{
}
}
In Objective c
[[[[[FIRDatabase database] referenceWithPath:#"users"] queryOrderedByChild:#"usernames"] queryEqualToValue:#"your User Name"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot)
{
if ([snapshot.valueInExportFormat isKindOfClass:[NSDictionary class]])
{
// User is exits
}
else
{
}
}];

Swift: how to retrieve data from firebase?

My structure in firebase is as follows:
app name
user ID
wins = 7
losses = 8
and my code to read the wins child node
ref = Database.database().reference().child(passUserID)
ref?.child("wins").observe(.childAdded, with: { (snapshot) in
//Convert the info of the data into a string variable
let getData = snapshot.value as? String
print(getData)
})
But it prints nothing.
To read data from Firebase you attach a listener to a path which is what creates a FIRDatabase reference. A FIRDatabaseReference represents a particular location in your Firebase Database where there is a key-value pair list of children. So in your case, you have created a Firebase reference to the key "wins" which only points to a value and not a key-value pair. Your reference was valid up to this point:
ref = Database.database().reference().child(passUserID)
//did you mean FIRDatabase and not Database??
This FIRDatabaseReference points to the key passUserID which has a key-value pair list of children ["wins":"7"] and ["losses":"8"] (NOTE: a key is always a string). So from your FIRDatabase reference, you create your observer as follows and read the value of "wins":
ref?.observe(.childAdded, with: { (snapshot) in
//Convert the info of the data into a string variable
if let getData = snapshot.value as? [String:Any] {
print(getData)
let wins = getData["wins"] as? String
print("\(wins)")
}
})
The Child added event will fire off once per existing piece of data, the snapshot value will be an individual record rather than the entire list like you would get with the value event. As more items come in, this event will fire off with each item. So if "losses" is the first record you might not get the value of "wins". Is this what you are trying to achieve? If what you really wanted to know is the value of "wins" at that particular location and to know if this value has ever changed you should use the .value observer as follows:
ref?.observe(.value, with: { (snapshot) in
//Convert the info of the data into a string variable
if let getData = snapshot.value as? [String:Any] {
let wins = getData["wins"] as? String
print("\(wins)") //check the value of wins is correct
}
})
Or if you just wanted to get the know the value of wins just once and you are not worried about knowing if there any changes to it, use the "observeSingleEvent" instead of "observe".
EDIT
I saw your image and now realize you might also have a problem with your reference. Your ref should actually be something like:
ref = FIRDatabase.database().reference().child("game-").child(passUserID)
You have obscured what "game" is but a valid reference to "wins" will include it.
SECOND EDIT
I will add the following so you can properly debug the problem. Use this pattern to observe the value and see if you get an error returned and what is says:
ref.observe(.value, with: { (snapshot) in
print(snapshot)
}, withCancel: { (error) in
print(error.localizedDescription)
})
Normally it will give you an error if you cannot access that Firebase location because of a database rule. It will also be a good idea to see if print(snapshot) returns anything as above.
You need this:
ref.child("YOUR_TOP_MOST_KEY").observe(.childAdded, with: { (snapshot) in
let keySnapshot = snapshot.key
//print(keySnapshot)
self.ref.child(keySnapshot).observe(.value, with: { (snapshot2) in
//print(snapshot2)
}) { (error) in
print("error###\(error)")
}
})

Pulling Data from Firebase into Array

I was recently told to structure my Firebase differently. Before I was putting everything related to a particular user under his or her tree. I was told however to flatten it and create his or her nodes separately and then to just link that node into that users tree when you need to.
So my tree looks like this
root
card
*card autoID*
nickname: "foo"
type: "bar"
user
*user uid*
card
*card autoID*: true
I am going to add more to the card as the user progresses through the app, and if I understand how I am supposed to structure the data I will be adding it to the the card node since that card is linked to the user.
My question is how do I pull data from Firebase then into say an array or a dictionary? If it was all in one tree I would do something like this
let ref = FIRDatabase.database().reference()
let user = FIRAuth.auth()?.currentUser
let userCard = ref.child((user?.uid)!).child("card")
But since that card under the user is only a reference how do I then go to the real place where the card is...the part that has the nickname and type?
Edit
So with some help from other SO posts, the documentation, and a friend I have the code 90% working.
What I am able to do is
1) find all of the card autoID under the user node that is associated to the user and store those strings into an array # 1
2) I am able to query all of the card autoID under the node card and then find the ones that match what is in array # 1 and store them in array # 2 (the rest are ignored)
3) **Here is where I am stuck. If I am inside of the .observe then I can do what I want with the array like printing its contents. HOWEVER, if I call print outside of the .observe I get nothing...
here is my code
func pullCurrentUserCardInfo() {
let userCardsRef = ref.child("users").child((user?.uid)!).child("cards")
userCardsRef.observeSingleEvent(of: .value, with: {(snapshot) in
if let snapDict = snapshot.value as? [String: AnyObject] {
for each in snapDict {
self.usersCardRefArray.append(each.key)
self.count = Int(snapshot.childrenCount)
}
}
})
self.ref.child("cards").observe(.value, with: { (snapshot) in
if snapshot.hasChildren() {
for item in snapshot.value as! [String: AnyObject] {
for test in self.usersCardRefArray {
if test == item.key {
self.allCurrentUsersCards.append(item.key)
}
}
}
} else {
print("no children")
}
})
}
if I were to say the following inside of the function but outside of the .observe ....}) then it doesn't do anything.....
for item in allCurrentUsersCards {
print(item)
}
Am I missing something small somewhere or is this something to do with firebase?
I think there's an unneeded level of complexity here. You do not need to store (in this use case at least) a separate card for each user. There's a 1-1 relationship between user and card so just storing the card data for each user within the user node would be the best answer.
However, to answer the question directly, here's how to do it. We going to slightly alter the Firebase structure:
root
cards
*user uid* <- CHANGE
nickname: "foo"
type: "bar"
users
user uid: true <- CHANGE
Since user uid's are always unique and created for you, leverage them when working with users. So in this case just store the user uid's in the user node and that same uid in the cards node.
Create a User Class and an array to store them in. This would typically be done right inside a viewController for example
class ViewController: UIViewController {
class UserClass {
var uid = ""
var nickname = ""
var type = ""
}
var usersArray = [UserClass]()
Then, craft a Firebase observer to populate the usersArray, getting each card for each user
//iterate over all of the users, get the user and its card data
let usersRef = ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for snap in snapshot.children { //iterate over all users
let userSnap = snapshot as! FIRDataSnapshot
let userKey = userSnap.key //the uid of each user
//now that we have the uid, get it's card data
let thisUserCardRef = cardsRef.child("uid")
thisUserCardRef.observeSingleEvent(of: .value, with: { userSnap in
let userCardSnap = userSnap as! FIRDataSnapshot
let userCardDict = userCardSnap.value as! [String:AnyObject]
let nickname = userCardDict["nickname"]
let type = userCardDict["type"]
let aUser = UserClass()
aUser.userKey = userKey
aUser.nickname = nickname
aUser.type = type
self.usersArray.append(aUser)
//In general, this is where the tableView is refreshed
// because the user data and card data is valid at this point
//usersTableView.reload data /
})
}
})
The key here is to remember that Firebase is asynchronous and that code is way faster than the internet. So this high level example will fail most of the time
func getData() {
loadDataFromFirebase()
print("data loaded, do something with it") //<- executes before the prior line completes
}
func loadDataFromFirebase() {
someRef.observeEvent...
print("data is now valid inside observe closure")
}
This will usually result in
data loaded, do something with it
data is now valid inside observe closure
Which is opposite of what is wanted. Code executes faster than the internet so the asynchronous observe closure will occur after the data loaded... is printed. Only reference and work with firebase data inside a closure and use the closure to pace your app.
If you notice in the first example code provided - we only work with the data once it's been returned from Firebase.
Also note that we completely eliminated queries! Queries are 'heavy' by comparison to observe events and since we are leveraging the uid of each user, it's path will be known, hence the change from a node created with childByAutoId to using the uid.

How to update value in Firebase with childByAutoId?

When I create objects in Firebase, I use childByAutoId. How can I update these specific objects later? I'm having trouble obtaining the value of the key Firebase automatically updates. Snapshot.key just returns "users". Here's my JSON structure:
{
"users" : {
"-KQaU9lVcUYzIo52LgmN" : {
"device" : "e456f740-023e-440a"
"name: "Test"
}
},
How can I get the -KQaU9lVcUYzIo52LgmN key? I want to update the device child. Here's what I have so far. It currently creates a completely separate snapshot with a single child.
self.rootRef.child("users").queryOrdered(byChild: "name").queryEqual(toValue: self.currentUser).observeSingleEvent(of: .value, with: { (snapshot) in
let key = self.rootRef.child("users").childByAutoId().key
let childValues = ["device": device]
self.rootRef.child("users").child(key).updateChildValues(childValues)
Edit: device is a string set further up in the code. Not defined in this scope (to make it easier to read for this question).
When you get Snapshot.key, it returns "users" because that is the overall key for your snapshot. Everything inside of "users" in your snapshot is considered the value.
You need to iterate over the child layers to dig down to "device".
Try this:
rootRef.child("users").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
var userKey = child.key as! String
if(userKey == userKeyYouWantToUpdateDeviceFor){
rootRef.child("users").child(userKey).child("device").setValue(device)
}
}
}
})
This code will do the following:
Gets snapshot of your reference (the key for that would be
'users').
Gets all the children (your user keys) and assigns them as another
snapshot to 'result'.
Checks each key one at a time until it finds the key you want (for
example, if you look for user with the key "-KQaU9lVcUYzIo52LgmN",
it will find it on the first iteration in your example code you
posted).
Once it finds that key, it sets the value for the device inside that
key with the line
rootRef.child("users").child(userKey).child("device").setValue(device).
Of course, you will need to store all your user keys when you make them. You can maybe use SharedPreferences on the device for this, but if it gets cleared for any reason then that data will just be sitting there. You could also store it on internal storage for your app, but SharedPreferences is what I would use.
Hope this helps!
snapshot has a property key which is
The key of the location that generated this FIRDataSnapshot.
And as you can see you are getting one (snapshot) by calling observeSingleEvent(of: .value, with: { (snapshot)...
so instead of let key = self.rootRef.child("users").childByAutoId().key
try to call let key = snapshot.key
childByAutoId().key always generates new unique key based on timestamp, that's why you are creating new child, not updating the one you want
Hope that works
I adapted Ryan's answer to my own issue (kinda similar) and figured out a way to update your device ID directly without needed to know/store the AutoID key generated by Firebase :
reference = Database.database().reference().child("users")
reference.observeSingleEvent(of: .value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
if child.childSnapshot(forPath: "device").value as? String == self.yourDeviceIDVariable {
print("### Device exists in Firebase at key = \(child.key)")
let childKey = child.key
self.reference.child(childKey).child("device").setValue(yourNewDeviceID)
}
}
}
})

Resources