I need to arrange my firebase data by date (unix). I thought queryOrdered(byChild: "date") would do the trick. Done a search and found this which makes sense:
But when you request the .value of the snapshot, the keys+data are converted to a Dictionary. Since a dictionary does not have an extra place to put the information about the order, that information is lost when converting to a dictionary.
Using the same json but modified with unix dates...:
{
"users" : {
"alovelace" : {
"name" : "Last Year",
"date" : 1480550400
},
"eclarke" : {
"name" : "New Year Now",
"date" : 1483228800
},
"ghopper" : {
"name" : "New Year",
"date" : 1483228800
}
}
}
... how to sort when my code is like this:
DataService.ds.REF_INCOMES.queryOrdered(byChild: "date").observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
print(snapshot)
for snap in snapshot {
if let incomeDict = snap.value as? [String: AnyObject] { // What needs to change here?
let key = snap.key
let income = Income(incomeId: key, incomeData: incomeDict)
self.incomes.append(income)
self.incomes.reverse()
}
}
}
self.tableView.reloadData()
})
The image below, "Last Year" should be last but it's not:
I have a ruby mentality so Im lost with swift. Thanks.
I think what you're missing here is the sort function, Please try the code below and let me know what are the results:
DataService.ds.REF_INCOMES.queryOrdered(byChild: "date").observe(.value, with: { (snapshot) in
guard let usersSnapshot = snapshot.value as? [String:NSObject] else{
return
}
let users = usersSnapshot["users"] as! [String:AnyObject]
let sorted = users.sorted{($0.0.value["date"] as! Double) > ($0.1.value["date"] as! Double)}
print(sorted) // It should be: New Year Now, New Year, Last Year
})
I believe the problem is that you are observing by .value, which essentially ignores the order by. Try to observe by .childAdded, which does respect the order by operation.
For more info, read the first "Pro Tip": https://howtofirebase.com/collection-queries-with-firebase-b95a0193745d
Related
How can I make this function to update the realtime database in Firebase? I managed to make the function work but only to write new ID's for posts when updateChild is triggered. How can I make it to update the current posts by their post ID?
var pathToPictures = [Pictures]()
func updateAllImagesOfCurrentUserInDatabase() {
//refrences
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
//match the user ID value stored into posts with current userID and get all the posts of the user
let update = ref.child("posts").queryOrdered(byChild: "userID").queryEqual(toValue: uid)
update.observe(FIRDataEventType.value, with: { (snapshot) in
// print(snapshot)
self.pathToPictures.removeAll()
if snapshot.key != nil {
let results = snapshot.value as! [String : AnyObject]
for (_, value) in results {
let pathToPostPicture = Pictures()
if let pathToImage = value["pathToImage"] as? String , let postID = value["postID"] as? String {
pathToPostPicture.postImageUrl = pathToImage
pathToPostPicture.postID = postID
self.pathToPictures.append(pathToPostPicture)
print("Image and POST ID: \(pathToPostPicture.postImageUrl!)")
print("Post ID is : \(postID)")
if FIRAuth.auth()?.currentUser?.uid == uid {
ref.child("Users_Details").child(uid!).child("profileImageUrl").observeSingleEvent(of: .value, with: { (userSnapshot) in
print(userSnapshot)
let userIMageUrl = userSnapshot.value as! String
pathToPostPicture.postImageUrl = userIMageUrl
self.pathToPictures.append(pathToPostPicture)
print("This is the image path:" + userIMageUrl + String(self.pathToPictures.count))
// Generate the path
let newPostRef = ref.child("posts").childByAutoId()
let newKey = newPostRef.key as String
print(newKey)
let updatedUserData = ["posts/\(postID)/pathToImage": pathToPostPicture.postImageUrl]
print("This is THE DATA:" , updatedUserData)
ref.updateChildValues(updatedUserData as Any as! [AnyHashable : Any])
})
}
print(self.pathToPictures.count)
}
}
} else {
print("snapshot is nill")
}
self.collectionView?.reloadData()
})
}
UPDATE: This is how my database looks like
This is my database:
"Users_Details" : {
"aR0nRArjWVOhHbBFB8yUfao64z62" : {
"profileImageUrl" : "url",
"userID" : "aR0nRArjWVOhHbBFB8yUfao64z62"
},
"oGxXznrS2DS4ic1ejcSfKB5UlIQ2" : {
"profileImageUrl" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
}
},
"posts" : {
"-KlzNLcofTgqJgfTaGN9" : {
"fullName" : "full name",
"interval" : 1.496785712879506E9,
"normalDate" : "Tue, 06 Jun 2017 22:48",
"pathToImage" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
},
"-KlzNXfvecIwBatXxmGW" : {
"fullName" : "full name",
"interval" : 1.496785761349721E9,
"normalDate" : "Tue, 06 Jun 2017 22:49",
"pathToImage" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
},
Let me tell you what it does: It's looping through the posts and finds all the current user posts. Then it takes the path to image url of every post and assign it to an array. After that it's finding the current user path to image url and updates the whole array with that. Now, I don't know how to update the database with the new values. If anybody knows it will be much appreciate it!
I am looking to make Atomic Writes Across Multiple Locations. Can somebody show me how to fix my code to do that?
It will look something like this:
// ATOMIC UPDATE HERE - IF SOMEBODY CAN FIND THE RIGHT WAY OF DOING THAT
// Generate a new push ID for the new post
let newPostRef = ref.child(byAppendingPath: "posts").childByAutoId()
let newPostKey = newPostRef.key
// Create the data we want to update
let updatedUserData = ["posts/\(newPostKey)": ["pathToImage": self.pathToPictures]] as [String : Any]
// Do a deep-path update
ref.updateChildValues(updatedUserData)
It looks like you're going to be downloading a lot of repetitive data. While denormalization of your database is a good practice, I would argue that in this case, you're better off not including the same download URL in every post. It doesn't make a difference across a handful of posts, but if you have thousands of users and tens or hundreds of thousands of posts, that's a lot of extra data being downloaded. Instead, you could have a dictionary containing uids as keys and the profile imageUrl as the value. You could check the dictionary for the desired uid, and if it's not present, query the database for that user's User_Details, then add them to the dictionary. When you need to display the image, you'd get the url from this dictionary. Someone else may have a better suggestion for this, so I welcome other ideas.
If you'd prefer to keep the profile image in every post, then I recommend using Cloud Functions for Firebase. You can make a function that, when profileImageUrl is updated in the user's User_Details, then updates this entry in the other posts. Currently, Cloud Functions are only available in Node.js. If you don't know JS, please don't let that be a deterrent! It's definitely worth it to learn a bit a JS. And the samples show you about as much JS as you'll have to know to get started.
Check out these resources:
Getting Started with Cloud Functions for Firebase
GitHub samples
Cloud Functions for Firebase documentation
Writing a Database Trigger
Writing a Cloud Storage Trigger: Part 1
Writing a Cloud Storage Trigger: Part 2
After struggling with the logic for a few days I finally got it. If anyone will encounter something like this, this is the answer. In my case it's about updating all the posts of the current user when user changes his image (my posts use denormalisation so every path to image is different). It will loop through the posts, find the current user postsID matching his UserID, place them into an array, finds the currentUser picture and update that array with the path to image. Finally will update the Firebase Database!
var pathToPictures = [Pictures]()
func updateAllImagesOfCurrentUserInDatabase() {
//refrences
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
//match the user ID value stored into posts with current userID and get all the posts of the user
let update = ref.child("posts").queryOrdered(byChild: "userID").queryEqual(toValue: uid)
update.observe(FIRDataEventType.value, with: { (snapshot) in
self.pathToPictures.removeAll()
if snapshot.value as? [String : AnyObject] != nil {
let results = snapshot.value as! [String : AnyObject]
for (_, value) in results {
let pathToPostPicture = Pictures()
if let pathToImage = value["pathToImage"] as? String , let postID = value["postID"] as? String {
pathToPostPicture.postImageUrl = pathToImage
pathToPostPicture.postID = postID
self.pathToPictures.append(pathToPostPicture)
print("Image and POST ID: \(pathToPostPicture.postImageUrl!)")
print("Post ID is : \(postID)")
if FIRAuth.auth()?.currentUser?.uid == uid {
ref.child("Users_Details").child(uid!).child("profileImageUrl").observeSingleEvent(of: .value, with: { (userSnapshot) in
print(userSnapshot)
let userIMageUrl = userSnapshot.value as! String
pathToPostPicture.postImageUrl = userIMageUrl
self.pathToPictures.append(pathToPostPicture)
print("This is the image path:" + userIMageUrl + String(self.pathToPictures.count))
// Update the Database
let postIDCount = pathToPostPicture.postID!
let updatedUserData = ["posts/\(postIDCount)/pathToImage": pathToPostPicture.postImageUrl!]
print("This is THE DATA:" , updatedUserData)
ref.updateChildValues(updatedUserData as Any as! [AnyHashable : Any])
})
}
print(self.pathToPictures.count)
}
}
} else {
print("snapshot is nill - add some data")
}
self.collectionView?.reloadData()
})
}
I'm trying to query my Firebase database to find users of a particular name or email. I've found several examples of how to do this, all of them have seemed relatively easy to follow, but none have worked as expected for me.
Here is an example of how my json data is structured.
{
"allUsers" : {
"uid0001" : {
"userInfo" : {
"email" : "firstName1.lastName1#email.com",
"firstName" : "firstName1",
"lastName" : "lastName1",
"uid" : "uid0001"
}
},
"uid0002" : {
"userInfo" : {
"email" : "firstName2.lastName2#email.com",
"firstName" : "firstName2",
"lastName" : "lastName2",
"uid" : "uid0002"
}
}
}
}
And here is a sample function of how I'm trying to query the database
func performQuery(forName queryText:String)
{
let key = "firstName"
let ref1 = firebaseDatabaseManager.allUsersRef.queryOrdered(byChild: queryText)
let ref2 = firebaseDatabaseManager.allUsersRef.queryEqual(toValue: queryText, childKey: key)
//ref.observeSingleEvent(of: .childAdded, with: {(snapshot) in
ref1.observe(.childAdded, with: {(snapshot) in
let userId = snapshot.key
if let dictionary = snapshot.value as? [String: AnyObject]
{
if let userInfo = dictionary["userInfo"] as? [String:AnyObject]
{
if
let email = userInfo["email"] as? String,
let firstName = userInfo["firstName"] as? String,
let lastName = userInfo["lastName"] as? String
{
let user = User.init(withFirst: firstName, last: lastName, userEmail: email, uid: userId)
}
}
}
})
}
You can see here I have two examples of how I'm structuring ref and two examples of how I'm observing the reference, although I've tried every possible combination that I can think of.
If I'm using ref.observe(....
The block will execute for all users at the node regardless of if queryText is actually present or not.
If I'm using ref.observeSingleEvent(of:....
The block will execute for the topmost user in the json structure.
On top of that, I've tried several variations of reference that return nothing at all.
Any help at all is appreciated!
Thanks
You need to combine queryOrderedByChild: and queryEqualToValue: to get the correct results:
let query = firebaseDatabaseManager.allUsersRef
.queryOrdered(byChild: "userInfo/" + key)
.queryEqual(toValue: queryText)
query.observe(.childAdded, ...
Try replacing
let ref2 = firebaseDatabaseManager.allUsersRef.queryEqual(toValue: queryText, childKey: key)
with
let ref2 = ref1.queryEqual(toValue: queryText)
and then call:
ref2.observe(.childAdded, with: {(snapshot) in
Since right now you are not looking for a certain user but for all users
This issue is the Firebase structure is (unnecessarily) too deep.
What you have is
"allUsers" : {
"uid0001" : {
"userInfo" : { <- This is the issue
"email" : "firstName1.lastName1#email.com",
"firstName" : "firstName1",
"lastName" : "lastName1",
"uid" : "uid0001"
}
},
It should (could) be
"allUsers" : {
"uid0001" : {
"email" : "firstName1.lastName1#email.com",
"firstName" : "firstName1",
"lastName" : "lastName1"
}
}
There's probably no need to have the userInfo node inside the uid0001 node.
Also, you probably don't need the uid stored in the node as well as using it as the key - when the node is returned you can always get the uid from the snapshot.key for each user.
That being said, you can actually do this with a deep query, but it doesn't appear to be needed in this case. (See Frank's answer as it is the correct solution for the structure posted in the question)
and to query for a specific first name using the structure I suggested
let fName = "firstName1"
let queryAllUsersRef = allUsersRef.queryOrdered(byChild: "firstName")
.queryEqual(toValue: fName)
//get all of the users with firstName1
queryRef.observeSingleEvent(of: .value, with: { snapshot in
//snapshot may return more than one user with that first name
// so iterate over the results
for snap in snapshot.children {
let userSnap = snap as! FIRDataSnapshot //each user is it's own snapshot
let userKey = commentSnap.key //the uid key of each user
let userDict = userSnap.value as! [String:AnyObject]
let email = userDict["email"] as! String
print("uid: \(userKey) has email: \(email)"
}
})
I need to make multiple observations, but I don't know how.
Here is my database structure:
"Posts" : {
"f934f8j3f8" : {
"data" : "",
"date" : "",
"userid" : ""
}
},
"Users" : {
"BusWttqaf9bWP224EQ6lOEJezLO2" : {
"Country" : "",
"DOB" : "",
"Posts" : {
"f934f8j3f8" : true
},
"Profilepic" : "",
"name" : "",
"phonenumber" : ""
}
I want to observe the posts and I write the code and it works great, but I also want to get the name of the user who posted this post but when I wrote save the name and use it it gives me null. Here is my code.
DataServices.ds.REF_POSTS.queryOrderedByKey().observe(.value,
with: { (snapshot) in
self.posts = []
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
DataServices.ds.REF_USERS.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let postusername = value?["name"] as? String ?? ""
})
print(" ------ User name : \(postusername) ------")
})
print(" ------ User name 2 : \(postusername) ------")
let post = Posts(postKey: key, postData: postsDict)
self.posts.append(post)
The first print statement prints the username, but the second one prints nothing.
Thanks in advance.
Firebase is asynchronous so you can't operate on a variable until Firebase populates it within it's closure. Additionally code is faster than the internet so any statements following a closure will occur before the statements within the closure.
The flow would be as follows
Query for the post {
get the user id from the post inside this closure
query for the user info {
create the post inside this second closure
append the data to the array inside this second closure
reload tableview etc inside this second closure
}
}
Something like this edited code
self.posts = []
myPostsRef.queryOrderedByKey().observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
myUsersRef.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userName = value?["name"] as? String ?? ""
let post = Posts(postKey: key, postData: postsDict, name:userName)
self.posts.append(post)
})
}
}
}
})
You're not using the postusername inside the closure so I added that to the Posts initialization.
Also, the self.posts = [] is going to reset the posts array any time there's a change in the posts node - you may want to consider loading the array first, and then watch for adds, changes, or deletes and just update the posts array with single changes instead of reloading the entire array each time.
Edit:
A comment was made about the data not being available outside the loop. Here is a very simplified and tested version. Clicking button one populates the array from Firebase with a series of strings, clicking button 2 prints the array.
var posts = [String]()
func doButton1Action() {
let postsRef = ref.child("posts")
self.posts = []
postsRef.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let value = snap.value as! String
self.posts.append(value)
}
}
})
}
func doButton2Action() {
print(posts)
}
I am now struggling with Firebase database.
For example here's my database structure
"Menus" : {
"Day1" : {
"mealDes" : "delicious Thai food",
"date" : "2016.10.20",
"mealname" : "kakoon"
},
and I need to set a variable to get the value of "date".
something like : print(theDateVar) //get 2016.10.20
I tried some observeSingleEvent and observe method, they're not working!
DataService.ds.REF_MENUS.observeSingleEvent(of: .value, with: { snapshot in
if let date = snapshot.childSnapshot(forPath: "menuDate") as? String {
print ("date is \(date)")
}
})
I know retrieving data from database is frequently asked,
but all the question i searched is not helping
please help me out :(, I spend half of my weekend on this but still...
UPDATE
Hi, it totally worked!
my code now is looks like the following :
FIRDatabase.database().reference().child("Menus/Day1").observeSingleEvent(of: .value, with: {(snap) in
if let snapDict = snap.value as? Dictionary <String, AnyObject>{
let date = snapDict["mealPic"] as! String
let OrderInfo = DataService.ds.REF_ORDER
let USERUID = FIRAuth.auth()!.currentUser!.uid
let userinfo = OrderInfo.child("date").child(USERUID).child("data")
userinfo.child("address").setValue("SomeRandomAddress")
userinfo.child("phonenumber").setValue("666888")
}
})
but what if I want to set the address value and the phone number value as the value i have in other node (user)
"Menus": {
"User" : {
"WbxT2bBgcnYOEn2YUqpvh4mtxvo2" : {
"address" : "taipei City ",
"phonenumber" : "0933555666",
"provider" : "Firebase"
}
do i use observeSingleEvent again in observeSingleEvent ?
Try using :-
FIRDatabase.database().reference().child("menus/Day1").observeSingleEvent(of: .value, with: {(snap) in
print(snap)
if let snapDict = snap.value as? [String:AnyObject]{
let date = snapDict["date"] as! String
print(date)
}
})
I tried to convert my code func by func to Swift 3. I have to say that I had fully working project before. Now I have problem where I have no errors and just some warnings but some of the functions are not being executed. What should cause this?
I only assume that those given functions are faulty because these are the parts where I am not getting anything even print.
These are some of my functions that worked before but not with Swift 3:
//With this I get selected brand products values like product name, nicotine, flavor etc..
let ref = FIRDatabase.database().reference().child("Snuses").queryOrdered(byChild: "Brand").queryEqual(toValue: brandName)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
if let products = (snapshot.value as AnyObject).allValues as? [[String:AnyObject]]{
self.productsValue = products
self.productsTable.reloadData()
}
}
})
//With this fucntion I get the products count.
let ref = FIRDatabase.database().reference().child("Snuses").queryOrdered(byChild: "Brand").queryEqual(toValue: filteredBrands[indexPath.row])
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
if let products = (snapshot.value as AnyObject).allValues as? [[String:AnyObject]]{
var count = (snapshot.childrenCount)
snusProductCountLabel.text = "\(count) products"
}
}
})
//Parse snus brands
func parseSnuses(){
let ref = FIRDatabase.database().reference().child("Brands").queryOrderedByKey()
ref.observe(.childAdded, with: { (snapshot) in
self.brands.append(snapshot.key)
print(snapshot.key)
self.snusBrandsTableView.reloadData()
}){ (error) in
}
Anything I can do different please tell me! Those functions are in different ViewControllers.
Edit: this is my JSON tree
{
"Snuses" : {
"Catch Eucalyptus White Large" : {
"Brand" : "Catch",
"Products" : "Catch Eucalyptus White Large",
"PorionWeight" : 21.6,
"flavor" : "Tobacco, Eucalyptus",
"nicotine" : 8.0,
"PortionsCan" : 24,
"shipping weight" : 39
},
And these are security rules:
{
"rules": {
".read": "true",
".write": "true",
"Snuses": {
".indexOn": "Brand"
}
}
}
I believe the
if let products = (snapshot.value as AnyObject)
.allValues as? [[String:AnyObject]]{
is the issue.
Try this as a test to see if it prints the data from the snapshot:
let ref = FIRDatabase.database().reference().child("Snuses")
.queryOrdered(byChild: "Brand").queryEqual(toValue: brandName)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
let dict = snapshot?.value as! [String: [String:String]]
let productsArray = Array(dict)
for row in productsArray {
print(row)
}
}
})
for a non-swifty test, you can also try this inside the closure instead of the above
let d2 = snapshot?.value as! NSDictionary
let a2 = d2.allValues
for r2 in a2 {
print(r2)
}
one more option:
let q = snapshot?.value as! [String: AnyObject]
let a3 = Array(q)
for r3 in a3 {
print(r3)
}
I don't know what your tableView is expecting in the array but one of those should cover it.