Firebase - Atomic writes Across Multiple Locations not working - ios

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()
})
}

Related

Swift Firebase Save/Update multiple parents with the same child values

I have an array of strings which is the "uid's" of users. I am trying to append data/children to these multiple "uid's". Adding children or updating children to individual parents/users is easy and I understand how to do it. The problem is that this array can either contain 1 uid or 50 uid's. Is it possible for me to take these uid's and then update them with the same value? I am unsure what code to provide since I am just trying everything to attack this.
With the code below, this is me send a message to other users.
Array of uid strings
var data = [String]()
Sample code of me sending a message to 2 users, just wanted to provide something here to show I know how to update/save data
private func sendMessageWithProperties(_ properties: [String: Any]) {
let businessRef = Database.database().reference().child("Business Group Chats Messages").child((group?.uid)!).child((Auth.auth().currentUser?.uid)!)
let ref = Database.database().reference().child("Business Group Chats Messages").child((Auth.auth().currentUser?.uid)!).child((group?.businessName)!)
let businesChildRef = businessRef.childByAutoId()
let childRef = ref.childByAutoId()
let fromID = Auth.auth().currentUser!.uid
let timeStamp = Int(Date().timeIntervalSince1970)
var value:[String: Any] = ["fromId" : fromID, "timeStamp" : timeStamp, "name": self.loggedInUserData?["name"] as? String]
properties.forEach { (k,v) in
value[k] = v
}
childRef.updateChildValues(value) { (err, ref) in
if err != nil {
print(err!)
return
}
Database.database().reference().child("Business Group Chats").child((self.group?.uid)!).child((Auth.auth().currentUser?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp, "businessName":(self.group?.businessName)!])
Database.database().reference().child("Business Group Chats").child((Auth.auth().currentUser?.uid)!).child((self.group?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp])
self.inputContainerView.inputTextField.text = nil
}
}
Here is me taking that array of "uid's" and then pulling and printing that I can access each "uid" through a array of strings. Allowing me to access, now I can append data to each.
Database.database().reference().child("Businesses").observe(.value, with: { snapshot in
if snapshot.exists() {
self.businessUID = snapshot.value as? NSDictionary
if let dict = snapshot.value as? NSDictionary {
for item in dict {
let json = JSON(item.value)
let businessUid = json["uid"].stringValue
for uid in self.data {
if uid == businessUid {
//print(uid)
self.businessessuids = uid
print(self.businessessuids)
Database.database().reference().child("Businesses").child(self.businessessuids).observe(.value, with: { snapshot in
print(snapshot)
print("Trying to pull data from multiple strings right here this shoudld work")
})
print("printing the values to match with the string UID's")
}
}
}
}
} else {
print("does not exist")
}
})

Finding it difficult to update value in firebase in Swift

My Firebase database looks like this
Here I got a field name user_post. Then I am adding a new post using childByAutoId() . Then inside that I am inserting the post according the postId . Now I want to update the like and add the new field peopleWhoLike. Could anybody tell me how to insert or update a value of any particular post. The sequence is
user_post > childByAutoId() > postId
Here is my code:
let keyToPost = ref.child("user_post").childByAutoId().key
ref.child("user_post").queryOrdered(byChild: self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value as? [String : AnyObject]) != nil {
let updateLikes: [String : Any] = ["peopleWhoLike/\(keyToPost)" : FIRAuth.auth()!.currentUser!.uid]
ref.child("user_post").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("user_post").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as? [String : AnyObject] {
if let likes = properties["peopleWhoLike"] as? [String : AnyObject] {
let count = likes.count
let update = ["Like" : count]
ref.child("user_post").child(self.postID).updateChildValues(update)
self.likeBtn.isHidden = true
self.unlikeBtn.isHidden = false
self.likeBtn.isEnabled = true
}
}
})
}
})
}//xxx
})
ref.removeAllObservers()
Here is how you can update one value at a time for example,
let ref : FIRDatabaseReference!
ref = FIRDatabase.database().reference()
ref.child("yourNode").child("subNode").updateChildValues(["yourKey": yourValue])
What I did was write the key to my posts. When a post was created a took the childByAutoID and added that to the post data so I could reference it later. I would reference it as "key" and the value would be the childByAutoId string. Once I had that key I would be able to add a like, like this,
on click
ref.child("yourNode").child(postKey).updateChildValues(["key": yourValue])
You could make a new node at the root called "Likes" whenever a user likes or unlikes it would take the postKey and create a subNode under the Likes node and then add the userId. When the post would load you would go into the Likes nodes and match the posts and then check it that user has liked or not.
Here I have user-watchlists but it could be the same as likes. In my app when a user saves a post. I record the userId under watchlists and in the userId is the postId that was saved. This could be similar to likes. Then I just compare and see if there is a match.

Firebase Query using Swift not working as expected

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)"
}
})

Firebase Swift syntax

Can anyone help me convert this from JavaScript to Swift 3 syntax?
I am trying to get all of the clients a specific user has.
I have clients saved in their own node, then I have a list of clientIDs in each of the users.
I believe this code will work, as a similar situation was described in the guide it comes from, data organized in the same way, but it is not swift syntax, particularly .on and .once.
var usersREF =
new Firebase("https://awesome.firebaseio-demo.com/users");
var clientsREF =
new Firebase("https://awesome.firebaseio-demo.com/clients");
var currentUsersClients = userREF.child("userID").child("comments");
currentUsersClients.on("child_added", function(snap) {
clientsREF.child(snap.key()).once("value", function() {
// Render the clients on the link page.
));
});
Here is the data Structure on Firebase:
I suppose other ways to do it might be to grab the current Users clientIDs, then do a call for all clients. Filter them via the clientIDs.
Or
Grab the current users clientIDs, then loop through them, making specific calls to each individual client using the ID.
I just don't know if it is bad practice to make multiple calls like that to firebase within a for loop. Or even worse, to pull down much much more data than is neccesary then filter it.
Thanks for any help!
This will get the clients for uid_0, based on your structure
let userRef = ref.child("users/uid_0")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String:AnyObject]
let clientsDict = userDict["clients"] as! [String:AnyObject]
for client in clientsDict {
let clientId = client.key
print(clientId)
}
})
Use this sample code to get your desired result
func getUsers(forUserID userID: String, completion: #escaping (User) -> Swift.Void, failure: #escaping () -> ()) {
if Auth.auth().currentUser != nil {
Database.database().reference().child("users").child(userID).observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
let receivedMessage = snapshot.value as! [String: Any]
let name = receivedMessage["name"] as? String ?? ""
let id = receivedMessage["id"] as? Double ?? 0.0
let profileurl = receivedMessage["url"] as? String ?? ""
completion(User(name: name, id: id, url: url))
} else {
failure()
}
})
} else {
failure()
}
}

How to make multiple observations with Firebase?

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)
}

Resources