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.
Related
For a few days I have been trying to read my data from firebase without success.
Indeed it is a set of tables also containing tables.
This function is to retrieve the subjects and at the same time the paragraphs
func getSubjects() {
let subjectRef = database.child("subjects")
subjectRef.observe(.childAdded, with: { (snapshot) in
for child in snapshot.children {
print(snapshot)
if let snapshot = child as? DataSnapshot,
let subject = Subject(snapshot.value)
//subjectList.append(subject)
// print("Data : \(subject)")
}
})
}
This is the firebase screen
Console screen
On Android I didn't have this problem, but since I'm new to iOS, I'm having a hard time coping.
Any help will be welcome. Thank you
At the moment, you are observing the database for constant changes and it will only run when a child/value has been added into the place you're currently checking, for this you may only want to retrieve a value once, and every time that view is loaded then it will fetch from the database again. It's a lot more efficient and less costly. You may want something like this:
ref = Database.database().reference()
ref.child("subjects").child("0").child("paragraphs").child("0").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let location = value["location"] as? NSDictionary
let title= value?["title"] as? String ?? ""
let text = value?["text"] as? String ?? ""
let latitude = location?["latitude"] as? String ?? ""
let longitude = location?["longitude "] as? String ?? ""
}) { (error) in
print(error.localizedDescription)
}
You think each child with nodes inside it as an array, or a json object. You can cast them into an NSDictionary and use that cast to access values inside them if they're nested.
If they're not nested and in the same level as the place you're watching in the database ref, like for instance, above we are looking in the subjects > 0 > paragraphs > 0 node within the database. Title is a value inside that node and not a child so we can simply get the value of title from the database through the data snapshot given back.
I recommend reading the Docs, they're very good and easy to understand when working with different OS/languages.
I am trying to do a conditional within Firebase Observer.
Essentially I like to check if seat is occupied.
If it is, then orders can be retrieved.
If not then send the restaurant back to the search seat page again.
For some reason, the code within if !taken is never executed even if the condition is met (ie. the owner has inputted the wrong seat number). I have put it within the closure, it should run right?
func retrieveData (){
var taken = false
var seatNumber = "**an Int from other page**"
let refCustomer = Database.database().reference().child("Restaurant").child("Customers")
refCustomer.queryOrdered(byChild: "Seat").queryEqual(toValue: "\(seatNumber)").observeSingleEvent(of: .childAdded, with: { (snapshot) in
if snapshot.exists() {
taken = true
let snapshotValue = snapshot.value as? [String : AnyObject] ?? [:]
self.customerFirstName = snapshotValue["Firstname"] as! String
self.customerLastName = snapshotValue["Lastname"] as! String
self.customerAllergy = snapshotValue["Allergy"] as! String
self.customerID = snapshot.key
self.allergy.text = self.customerAllergy
self.ptname.text = "\(self.customerFirstName) \(self.customerLastName)"
}
if !taken {
print ("oops")
self.performSegue(withIdentifier: "MainPage", sender: self)
}
})
}
There are a number of issues with this code, and possibly your structure so let me see if I can point you in the right direction.
First, you can eliminate the taken varable as it's unneeded. In short
if snapshot.exists() {
//handle the snapshot
} else { //use else here as if the snapshot doesn't exist we want this code to run
print ("oops")
}
Second, ensure your structure is like this
Customers
cust_0
Seat: 1
cust_1
Seat: 2
etc
Third, this is a string of "1"
queryEqual(toValue: "\(seatNumber)")
and you want to query for an Int so make it
queryEqual(toValue: seatNumber)
which queries for an Int of 1
Forth:
When querying Firebase, closures will not execute when .childAdded doesn't find anything. You should use .value.
From the docs of .childAdded
This event is triggered once for each existing child and then again
every time a new child is added to the specified path.
So if no child nodes match the query, it will not execute.
Use this
refCustomer.queryOrdered(byChild: "Seat")
.queryEqual(toValue: seatNumber)
.observeSingleEvent(of: .value, with: { (snapshot) in
And... this is the important part, .value retrieves all nodes that match the query so you will need to iterate over those nodes to work with the child nodes. Assuming there would only ever be one match then you can do this
guard let allChildren = snapshot.children.allObjects as? [DataSnapshot] else {return}
let firstChild = allChildren.first
Fifth. While technically this is ok
let f = snapshotValue["Firstname"] as! String
You are guaranteeing that a Firstname node always exists. If that's true go with it. However, a safer, more Swifty way would be to do this
let f = snapshotValue["Firstname"] as? String ?? "No First Name"
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)
My database has values sorted like this :
Users
UID
Username
Email
I'm wanting to implement a friend adding system where you search for either a username or email and it lets you add the person.
I'm able to locate users by using
REF_USERS.queryOrdered(byChild: "displayname").queryEqual(toValue: input).observeSingleEvent(of: .value) { (snapshot: FIRDataSnapshot) in {
print(snapshot.value)
}
With that I get the user's entire dictionary, but I'm having an issue grabbing the UID.
snapshot.key gives me "Users".
How can I grab the UID value out of the dictionary after finding the user's dictionary with either their username/email?
Try this...
Assume a structure (this is Swift 2, Firebase 2)
users
uid_0
email: "someuser#thing.com"
displayName: "some display name"
and we want to get uid_0's info
let displayName = "some display name"
usersRef.queryOrdered(byChild: "displayName").queryEqual(toValue: displayName)
.observeSingleEvent(of: .childAdded) { (snapshot: FIRDataSnapshot) in {
let dict = snapshot?.value as! [String: AnyObject]
let email = dict["email"]
let displayName = dict["displayName"]
print(email!)
print(displayName!)
let key = snapshot?.key
print(key!)
}
A couple things to note
The 'dict' variable is being told it's being assigned a dictionary of type [String: Anyobject].
Any Object could well, be any object. A String, another dictionary, an int. So you need to ensure you code can handle whatever the object is
The snapshot key in this case is the snapshot of this user, and the key must be the parent node, which in this case is uid_0. So the output is
someuser#thing.com
some display name
uid_0
EDIT:
Updated for Firebase 4, Swift 4 and handle the case where multiple children are returned
let usersRef = self.ref.child("users")
let input = "some display name"
let query = usersRef.queryOrdered(byChild: "displayName").queryEqual(toValue: input)
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let email = dict["email"] as! String
let displayName = dict["displayName"] as! String
print(email)
print(displayName)
let key = snapshot.key
print(key)
}
})
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
let query = REF_USERS.queryOrdered(byChild: "displayname").queryEqual(toValue: input)
query.observeSingleEvent(of: .value) { (snapshot: FIRDataSnapshot) in {
for child in snapshot.children {
print(child.key)
}
}
Also see:
Firebase snapshot.key not returning actual key?
Firebase access keys in queryOrderBy
Firebase access keys in queryOrderBy
Firebase getting data in order
I'm new to firebase and I have such structure of my firebase project
I want to get all objects, that "Interested" value is equal to "men"
I wrote such code, to get all object sorted by interes value:
let thisUserRef = URL_BASE.childByAppendingPath("profile")
thisUserRef.queryOrderedByChild("Interest")
.observeEventType(.Value, withBlock: { snapshot in
if let UserInterest = snapshot.value!["Interest"] as? String {
print (snapshot.key)
}
}
But I receive nil.
you need to loop through all the key-value profiles
if let allProfiles = snapshot.value as? [String:AnyObject] {
for (_,profile) in allProfiles {
print(profile);
let userInterest = profile["Interest"]
}
}
Here _ is the key that is in the format KYXA-random string and profile will be the element for that key.
Edit:
There is querying for child values as per the docs.
Try thisUserRef.queryOrderedByChild("Interest").equalTo("men") and then using the inner loop that i specified in the answer
This is a basic query in Firebase. (Updated for Swift 3, Firebase 4)
let profileRef = self.ref.child("profile")
profileRef.queryOrdered(byChild: "Interest").queryEqual(toValue: "men")
profileRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let dict = child as! [String: Any]
let name = dict["Name"] as! String
print(name)
}
})
The legacy documentation from Firebase really outlines how to work with queries: find it here
Legacy Firebase Queries
The new documentation is pretty thin.
Oh, just to point out the variable; thisUserNode should probably be profileRef as that's what you are actually query'ing.