Access Multiple Nodes in Firebase Database - ios

I am having a hard time pulling all my data from one of my nodes in my firebase database.
Here is how the node looks like in Firebase:
Considerations
-MEdUNZwVrsDW3dTrE6N
Company Description:
Company Image:
Company Name:
Decision Date:
Start Date:
Users
B2Z4DlZ8RucvEQhz2NSUkquqc5P2
Compensation:
PostNumber:
StoryNumber:
Under users there are going to be multiple people with different values for the compensation, post number, and storynumber. I have each user having a node called "user-considerations" that tags the unique id of the consideration each user is attached on and places it under their UID and tags a 1 next to it as the value. I am trying to access each specific user's info along with the other info in the node. Here is my code that I am using to call the information along with the struct I a using to capture the information:
STRUCT:
import UIKit
class ConsiderationInfo: NSObject {
var companyName: String?
var companyImage: String?
var companyDescription: String?
var decisionDate: String?
var startDate: String?
var compensation: String?
var postNumber: String?
var storyNumber: String?
}
CODE FOR OBSERVING INFO:
func updateConsiderationsArray() {
let uid = Auth.auth().currentUser?.uid
let ref = Database.database().reference().child("user-considerations").child(uid!)
ref.observe(.childAdded, with: { (snapshot) in
let considerationId = snapshot.key
let considerationReference = Database.database().reference().child("Considerations").child(considerationId)
considerationReference.observe(.value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let considerationInfo = ConsiderationInfo()
//self.setValuesForKeys(dictionary)
considerationInfo.companyName = dictionary["Company Name"] as? String
considerationInfo.companyImage = dictionary["Company Image"] as? String
considerationInfo.companyDescription = dictionary["Company Description"] as? String
considerationInfo.decisionDate = dictionary["Decision Date"] as? String
considerationInfo.startDate = dictionary["Start Date"] as? String
self.considerationsInfo.append(considerationInfo)
self.considerationName.append(considerationInfo.companyName!)
self.filteredConsiderations.append(considerationInfo.companyName!)
self.considerationCollectionView.reloadData()
}
}, withCancel: nil)
})
}
I am trying to access the information under the user specific node, i.e. the specific user's compensation post number and story number. I am unaware of how to access all of this to append the struct.
Here is the node with the user-considerations:

As it sits, I am really not seeing anything super wrong with the code but there are few things that could be changed to make it more streamlined.
I would first change the Consideration Info class to make it more self contained. Add a convenience initializer to handle a firebase snapshot directly with some error checking.
class ConsiderationInfo: NSObject {
var companyName = ""
convenience init(withSnapshot: DataSnapshot) {
self.init()
self.companyName = withSnapshot.childSnapshot(forPath: "Company Name").value as? String ?? "No Company Name"
}
}
I would also suggest removing the .childAdded and .observe events unless you specifically want to be notified of future changes. Use .value and .observeSingleEvent instead. Keeping in mind that .childAdded iterates over each node in your database one at a time - .value reads them in all at the same time. If there is a limited amount of data, .value works well.
func updateConsiderationsArray() {
let fbRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
let ref = fbRef.child("user_considerations").child(uid)
ref.observeSingleEvent(of: .value, with: { snapshot in
let allUidsSnap = snapshot.children.allObjects as! [DataSnapshot]
for uidSnap in allUidsSnap {
let considerationId = uidSnap.key
let considerationReference = fbRef.child("Considerations").child(considerationId)
considerationReference.observeSingleEvent(of: .value, with: { snapshot in
let considerationInfo = ConsiderationInfo(withSnapshot: snapshot)
self.considerationArray.append(considerationInfo)
// update your collectionView
})
}
})
}
What I am doing in the above is reading in the single node from user_considerations, which looks like this according to your quuestion
user_considerations
some_user_uid
a_user_uid
a_user_uid
and then mapping each child user to an array to maintain order
let allUidsSnap = snapshot.children.allObjects as! [DataSnapshot]
and then iterating over each, getting the uid (the key) of each node and getting that nodes data from the Considerations node.

Related

What could cause a Swift/Firebase app to act up the first time it is installed and never again?

What doesn't work the first time?:
The order in which database entries that I fetch displays
I am running this
1st part In ViewDiDLoad
let thisUserRef = Database.database().reference().child("users").child(uid)
let myPeopleRef = thisUserRef.child("likers")
myPeopleRef.queryLimited(toLast: 30).observeSingleEvent(of: .value, with: { snapshot in
let userArray = snapshot.children.allObjects as! [DataSnapshot]
for person in userArray.reversed() where uid == Auth.auth().currentUser?.uid {
let personUid = person.value as! String
self.printPersonInfo(uid: personUid) //// this calls a DispatchQueue.main.async that appends the data in the array and reloads
}
})
func printPersonInfo(uid: String) {
print(uid)
let usersRef = Database.database().reference().child("users")
let thisUser = usersRef.child(uid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let xx = snapshot.childSnapshot(forPath: "xx").value as? String ?? "No Entry"
let yy = snapshot.childSnapshot(forPath: "yy").value as? String ?? "No Entry"
let rr = snapshot.childSnapshot(forPath: "rr").value as? String ?? "No Entry"
let zz = snapshot.childSnapshot(forPath: "zz").value as? String ?? "No Entry"
let ll = snapshot.childSnapshot(forPath: "ll").value as? String ?? "No Entry"
let p = Usery(xx: xx, yy: yy, rr: rr, zz: zz, ll: ll)
self.person.append(p)
print(self.person, "person")
self.table.reloadData()
})
}
//////this gets the various data elements of the users that get displayed.
///////So to summarize, the query determines which users get displayed. This func determined which data of those users gets displayed.
Example of database entires of last 30
user K
user B
user F
user G
user K
user B
user K
.....
The only time this doesn't work is if you install the app clean for the first time. Then the order in which it displays is
user K
user K
user B
user B
user F
user G
JSON
users
uid
likers
chailbyAutoID: uid1
chailbyAutoID: uid2
One possible solution is to use DispatchGroups to control the loading of the data, which therefore would control the sequence in which a class var array is populated.
In this example, we'll read all of the users uids from a users node and populate an array. It would look like this
users
uid_0
name: "Wilbur"
uid_1:
name: "Orville"
We'll then go back and re-read additional user data from that node, using array.reversed() so they populate a result array in reversed order. We'll need a class to store each user data and then a class array to store all of the users are they are read in. Here's the class vars
struct PersonClass {
var uid = ""
var name = ""
}
var personArray = [PersonClass]()
and the code to populate them in reverse order
func readUserDataUsingDispatch() {
let ref = self.ref.child("users") //self.ref points to my firebase
ref.observeSingleEvent(of: .value, with: { snapshot in
//this just builds and array of user id's - we'll iterate through them to read the rest of the data
let idArray = snapshot.children.allObjects as! [DataSnapshot]
var allKeys = [String]()
for userSnap in idArray {
allKeys.append( userSnap.key ) //[uid_0, uid_1] etc
}
let group = DispatchGroup()
for uid in allKeys.reversed() {
group.enter()
let thisUser = ref.child(uid)
thisUser.observeSingleEvent(of: .value, with: { userSnapshot in
let name = userSnapshot.childSnapshot(forPath: "name").value as! String
let person = PersonClass(uid: uid, name: name)
self.personArray.append(person)
print("added uid")
group.leave()
})
}
group.notify(queue: .main) {
print("done")
}
})
}
when you run the code this is the output, indicating the data is loaded
added uid
added uid
done
and then when printing the class var personArray it's the users in reverse order.
uid_1 Orville
uid_0 Wilbur

How to retrieve data from different DatabaseReference at same time

I have places in different cities in my real time database (firebase).
Following is my database structure.
But according to firebase tutorial, I tried following code to get data
cityA.queryOrdered(byChild: "completed").observe(.value, with: { snapShot in
var newItems: [AttractionPlace] = []
for child in snapShot.children {
if let snapShot = child as? DataSnapshot,
let attraction = AttractionPlace(snapShot: snapShot) {
newItems.append(attraction)
}
}
print(newItems.count)
})
I also would like to get data from cityB at the SAME TIME and no idea how to retrieve. I know I can repeat same action to cityB. But is there any better way?
I'm not sure how you want your data structured. Can you show me what an AttractionPlace is supposed to look like? The code below does what you want appending the name of each place to newItems, but without specifying from which city it is. Take notice that I did not use your AttractionPlace object.
let dataref = Database.database().reference()
dataref.child("attractions").observe(.value, with: { (snapshot) in
var newItems = [AnyObject]()
for city in snapshot.children.allObjects as! [DataSnapshot] {
let value = city.value as? NSDictionary ?? [:]
for child in value {
let attraction = child.value
newItems.append(attraction as AnyObject)
}
}
print("newItems: ",newItems)
print("newItems.count: ",newItems.count)
})
Result:
newItems: [Place 3, Place 4, Place 1, Place 2]
newItems.count: 4

Firebase query results are mistaken

This is my database design.
foodie-ab2b4{
Foods{
0{
FoodName: "Baked Beans In Tomato Sauce"
FoodRecipe:
FoodUri:
Image:
}
1{
FoodName: "Another bean | Bubbling Bacon Butter Beans recipes"
FoodRecipe:
FoodUri:
Image:
}
}
}
I'm working on an ios project and this is how my firebase json structured.
let ref = Database.database().reference()
func searchFoodByName(FoodName: String){
let foodsRef = ref.child("Foods")
let input = FoodName
let query = foodsRef.child(key).queryOrdered(byChild: "FoodName").queryEnding(atValue: input)
query.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let fName = dict["FoodName"] as! String
let fIngredients = dict["Ingredients"] as! [String]
print(fName)
print(fIngredients)
let key = snapshot.key
print(key)
}
})
}
I'm trying to filter my food objects by their names. I have 10 objects in my database. In most cases this search return true objects. But there is one example i can not solve.
When i query the "tomato" word i need to get 1 object. But there is two. Although there is no tomato word in my second object.
The two object is in my json file.
If it is necessary i can upload my whole JSON file to here.
You seem to assume that Firebase can filter based on strings that contain a value, but it can't. See Firebase query - Find item with child that contains string (and the many links from there).
What Firebase can do is search for string values that start with a certain value. You do that by using a combination of queryStarting(atValue:) and queryEnding(atValue:):
let query = foodsRef
.queryOrdered(byChild: "FoodName")
.queryStarting(atValue: input)
.queryEnding(atValue: input+"\\uf8ff")
If you use this query, and input is ``Baked`, it will only match the first food from your JSON.

Retrieving data from Firebase and using it in another Firebase database reference

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)

How to save data from query, in array of string, Firebase iOS?

I want to make a user query, removing the name of each, and go keeping the names in an array, then display them on a table. The problem is I can not keep them in the settlement, how can I fix it? Thus I make the query to extract the names of users:
var users = [String]()
let ref = Firebase(url:"https:mydatabase/users")
ref.queryOrderedByChild("name").observeEventType(.ChildAdded,
withBlock: { snapshot in
if let username = snapshot.value["name"] as! String {
self.users.append(username)
print("username")
}
})
So I have my users table in firebase
The username var does have the name, but when adding the content of the var the settlement, does not, at the time of execution of the application does not throw any errors.
There are just a few typo's your code
Try this:
let usersRef = self.myRootRef.childByAppendingPath("users")
usersRef.queryOrderedByChild("name").observeEventType(.ChildAdded, withBlock: { snapshot in
if let username = snapshot.value["name"] as? String {
self.userArray.append(username)
print("\(username)")
}
})
Be sure to define the userArray as a property of the class so it will be available to other functions.
class MyClass: NSObject {
let myRootRef = Firebase(url:"https://your-app.firebaseio.com")
var userArray = [String]()

Resources