Finding it difficult to update value in firebase in Swift - ios

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.

Related

Firebase - Atomic writes Across Multiple Locations not working

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

Can't load in values for tableview cell firebase

I'm trying to load in different values for each cell in a tableview. Currently, I load in a teamID, display it on the current cell, then use that ID to load in the other attributes of the team.
self.ref?.child("Teams").child(currentTeamID).child("Number").observeSingleEvent(of: .value, with: { (snapshot) in
let number1 = snapshot.value as? Int
if let teamNum = number1 {
Cell.teamNumber.text = "team " + String(teamNum)
//breakpoint
}
})
self.ref?.child("Teams").child(currentTeamID).child("memberCount").observeSingleEvent(of: .value, with: { (snapshot) in
let memcon = snapshot.value as? Int
if let membercount = memcon {
Cell.userCount.text = "Members: " + String(membercount)
//breakpoint
}
})
return Cell
My issues comes when trying to load in these other attributes. Should I be doing this is a different way? Right now it loads only the second .observeSingleEvent I have tried placing breakpoint where I indicated above, but only the second one ever gets hit. Do I need a separate reference or is there a way to load all the values from a parent object?
Thanks a whole bunch.
Added Firebase Structure:
ftc-scouting-app
Teams
Brophy Robotics
Name: "Brophy Robotics"
Number: "201"
Password: "bronco"
memberCount: 2
memberList
member1: "5ilQc8KlrERLAmtFXjWaOZLIcoC3"
member2: "syV9SS6S9hY8PyKBOC0VQ3NNv0v2"
Users
5ilQc8KlrERLAmtFXjWaOZLIcoC3
(User Info Values)
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
(User Info Values
The values I'm trying to load are the team number and the member count. I want to put them on the cell as it loads in each team that each user has. So, I just need it to load each value and put it on my custom table view cell that has all the fields for it. To clarify - I already know that it retrieves the team ID properly because it is able to put it on the cell.
The value currentTeamID is a value that I have already loaded in, and is the id (which is the same as the name) of the current cell's prospective team.
First, change the structure
ftc-scouting-app
Teams
Jyis9009kos0kslk //should be generated with childByAutoId()
Name: "Brophy Robotics"
Number: "201"
Password: "bronco"
memberCount: "2"
memberList:
5ilQc8KlrERLAmtFXjWaOZLIcoC3: true //uid as the key
syV9SS6S9hY8PyKBOC0VQ3NNv0v2: true
Users
5ilQc8KlrERLAmtFXjWaOZLIcoC3
(User Info Values)
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
(User Info Values)
Then, let's retrieve just the one team node and get some data
let teamsRef = self.ref.child("ftc-scouting-app").child("Teams")
let thisTeamRef = teamsRef.child("Jyis9009kos0kslk")
thisTeamRef.observeSingleEvent(of: .value, with: { snapshot in
let teamDict = snapshot.value as! [String: AnyObject]
let teamName = teamDict["Name"] as! String
print(teamName)
let memCount = teamDict["memberCount"] as! String
print(memCount)
let memberList = teamDict["memberList"] as! [String: AnyObject]
for user in memberList {
print(user.key)
}
})
and the output is
Brophy Robotics
2
5ilQc8KlrERLAmtFXjWaOZLIcoC3
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
each event events asynchronously. you should use completion block in your each event.
func getNumber (completion: #escaping (String)->()){self.ref?.child("Teams").child(currentTeamID).child("Number").observeSingleEvent(of: .value, with: { (snapshot) in
let number1 = snapshot.value as? Int
if let teamNum = number1 {
completion(String(teamNum))
}
})}
getNumber(completion: {(teamNum) in
self.ref?.child("Teams").child(currentTeamID).child("memberCount").observeSingleEvent(of: .value, with: { (snapshot) in
let memcon = snapshot.value as? Int
if let membercount = memcon {
Cell.teamNumber.text = "team " + teamNum
Cell.userCount.text = "Members: " + String(membercount)
//breakpoint
}
})
})

Setting user values from Firebase

I was trying to initialize the values in the User object but from another question it seems like it's not possible to do it there - now, trying to do the same thing in a view controller in viewDidLoad, I'm running into another error:
This is my call to Firebase in viewDidLoad (myUser is a global variable var myUser:User!):
let userRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").childByAutoId()
let userRefHandle: FIRDatabaseHandle?
userRefHandle = userRef.observe(.value, with: { (snapshot) -> Void in
let userData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
if (FIRAuth.auth()?.currentUser?.uid) != nil {
if let name = userData["name"] as! String!, name.characters.count > 0 {
let handle = userData["handle"] as! String!
let gender = userData["gender"] as! String!
let profilePicture = userData["profilePicture"] as! String!
let uid = userData["uid"] as! String!
// let rooms = userData["rooms"] as! [[String : AnyObject]]
myUser.uid = uid
myUser.uid = handle
myUser.uid = name
myUser.uid = profilePicture
myUser.uid = gender
// myUser.rooms = rooms
} else {
print("Error! Could not initialize User data from Firebase")
}
}
})
The end goal for this is so that when a user launches the app (already signed up and their info is in Firebase), their info is pulled from the database and set to the User object so the values can be used around the app (name, handle, profilePicture, etc.).
I'm getting the error: Could not cast value of type 'NSNull' (0x106d378c8) to 'NSDictionary' on the let userData = snapshot.value line.
This is what the user's data looks like in Firebase:
"users" : {
"-KgjW9PEvCPVqzn7T5pZ" : {
"gender" : "male",
"handle" : "TestHandle123",
"name" : "Timothy",
"profilePicture" : "https://graph.facebook.com/*removed*/picture?type=large&return_ssl_resources=1",
"uid" : "2q4VCKu1e7hiL84ObdzgQcQ0pH63"
}
}
I'm wondering if this is the correct way to set a user's values from Firebase, and if so, how to avoid the error?
in your code, you are looking for the profile picture using profile
let profilePicture = userData["profile"] as! String!
but in the data, you have profilePicture
Because you have forced the unwrap you will get this error when the key is not found.
You should make sure that you use the same key in both places. It might also be worth including defaults in case you ever have data issues
let profilePicture = userData["profile"] as? String ?? "default"
Since the user is already signed up you should store the key that references to the firebase user (e.g. via NSUserDefaults). In this case "-KgjW9PEvCPVqzn7T5pZ". Use this in the userRef instead of childByAutoId, which would generate a new child location with a new key and doesn't exists yet (hence the NSNull).
let userRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").child("-KgjW9PEvCPVqzn7T5pZ")

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

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