iOS Firebase -How to remove children with same key from different nodes - ios

I have a ref named Following. Under that ref there are 2 different userIds who are following the same user. If the user they are both following wants to delete their account I want to delete them from the Following node. Multi location update doesn't seem correct to achieve this.
How can it be done?
User kk8qFOIw... is the user who is deleting their account. Once deleted their keys should be removed from the other user's nodes.

This is how you can do it :
First get all the nodes where your id = 1 , then run a multipath update and set them to empty.
let userId = "yourUserId"
self.ref.child("following").queryOrdered(byChild: userId).queryEqual(toValue: 1).observeSingleEvent(of: .value) { (snasphot) in
guard let value = snasphot.value as? [String : Any] else {return}
var multipathUpdate = [String:Any]()
value.keys.forEach({ (key) in
multipathUpdate["following/"+key+"/"+userId] = [:]
})
self.ref.updateChildValues(multipathUpdate, withCompletionBlock: { (err, ref) in
})
}

Related

Swift -Firebase is it possible to Post and Observe at same time

When I want to observe I run:
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.observeSingleEvent(of: .value, with:{ (snapshot) in
let likes = snapshot.childrenCount
let totalLikes = Int(likes)
// display totalLikes
})
When I want to post I run:
let dict = [uid: 1]
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.updateChildValues(dict) { [weak self](error, ref) in
if let error = error { return }
// if no error then update the ref
})
When posting the completionBlock has 2 values:
withCompletionBlock: (Error?, DatabaseReference)
Is there a way that I can also observe the ref that I am posting to using the 2nd completionBlock value: DatabaseReference:
For eg once the user likes something I want to update the ref and display the new number of likes at the same time without having to run observeSingleEvent:
let dict = [uid: 1]
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.updateChildValues(dict) { (error, ref) in
if let error = error { return }
// *** somehow get the total amount of likes from the ref ***
// If possible I want to avoid this code below
likesRef.observeSingleEvent(of: .value, with:{ (snapshot) in
let likes = snapshot.childrenCount
let totalLikes = Int(likes)
// display totalLikes
})
})
Actually its not possible to get both Event at same time. Once you like once your data reach at firebase database then you will get Value,Change,ChieldAdded event on App side. It will take 1-2 second or less depend on Internet. So Post will call first and then observer will call.
You can get observer this by two ways:
Step 1 : Add Child Change listener
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.observe(.childChanged, with: {
(snapshot) in
let likes = snapshot.childrenCount
let totalLikes = Double(Int(likes))
// display likes
})
Child change listener listens changes on snapshot
Step 2 : Put value listener
let likesRef = Database.database().reference().child("likes").child(postId)
likesRef.observe(.value, with: {
(snapshot) in
let likes = snapshot.childrenCount
let totalLikes = Double(Int(likes))
// display likes
})
Value listener listens every time
I Hope this will help...
Here's some very abbreviated code to accomplish the task of adding a users uid who likes a post and incrementing a counter via runTransactionBlock. Keep in mind there are 100 ways to do this.
A proposed structure
all_posts
post_0
like_count: 2
post: "Hello, World"
likes
post_0
uid_0: true
uid_1: true
Based on the question and discussion, I broke the posts and likes into separate nodes. The reasoning is that when the posts are loaded, they will probably be displayed in a list and all that's really needed at that point is the post topic and the total number of likes, so I made 'like_count' part of the post node.
If we added the actual likes within the post node itself, it could be 10,000 or 10,000,000 and there's no reason to load all of that data when loading in the posts. Having that many likes could also overwhelm the device.
Within the likes node, each post key references the post in the all_posts node. The children are the uid's of the users that liked the post and the value is a boolean 'true' as a placeholder. Nodes with no values cannot exist so 'true' keeps it in place and is a tiny amount of data.
Then the code -
func updatePostWithLikesViaTransaction() {
let postsRef = self.ref.child("all_posts") //self.ref points to my Firebase
let thisPostRef = postsRef.child("post_0")
thisPostRef.runTransactionBlock ({ (currentData: MutableData) -> TransactionResult in
if var data = currentData.value as? [String: Any] {
var count = data["like_count"] as? Int ?? 0
count += 1
data["like_count"] = count
currentData.value = data
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
return
}
print("successfully incremented counter")
let likesRef = self.ref.child("likes")
let likesPostRef = likesRef.child(thisPostRef.key!)
likesPostRef.child("uid_2").setValue(true)
}
}
The above is not ideal by any means but demonstrates the process (and works with the presented structure).
First we get a reference to the post we want to update and then within a transaction block read the currentData 'like_count' child which would return 2 based on the values in the structure. That's incremented to 3, updated within currentData and then updated via the TransactionResult.
runTransactionBlock also has an optional completion callback and we are using that to, upon success, update the post within the likes with the users uid and true.
The end result of running this is that the counter is incremeted to 3 and 'uid_2: true' is added to the 'likes/post_0' node

Access nested Firebase data in Swift

I am trying to access nested data values based on profile uid. with this One profile based on uid has multiple friends and friends have location and profile image (in profile) object.
I am using CodableFirebase and models for Friend,Profile,LastLocation. for main data list I use observeSingleEvent(of: .value and for all other nested data I need to use .observe(.childAdded .
Second thing How would I read all values based on profile uid and save them into an Array that I can then display in a TableView ?
I am not getting an idea how to do that please anyone help me.
Firebase Database:
zzV6DQSXUyUkPHgENDbZ9EjXVBj2
 friends
 FTgzbZ9uWBTkiZK9kqLZaAIhEDv1
 conversationUid: "-L_w2yi8gh49GppDP3r5"
 friendStatus: "STATUS_ACCEPTED"
 notify: true
 phoneNumber: "+055441503"
 uid: "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
 HHCBYyP4KybcINgZaWVukR967l12
 IcBfQKAnswQ7rivFuZsPvRUcsX43
 LEZ2FUxS1WVaqSEHx5YqnpZ5Els1
 YyFIm4hTorMFq4olpvHJI7e5e3a2
 nkS2cjSvmLUEYEKsAVyErz9HkrJ2
 lastLocation
 batteryStatus: 27
 latitude: 41.0220811
 longitude: 29.0445012
 timeStamp: 1571742057730
 uid: "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
 profile
 fcmToken: "enMneewiGgg:APA91bHyA4HypWUYhxGTUTTcWch8ZJ_6UUW..."
 name: “My Profile name”
 phoneNumber: "+05588674"
 picture: "profile/zzV6DQSXUyUkPHgENDbZ9EjXVBj2/a995c7f3-7..."
 uid: "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
Code:
let ref = Database.database().reference()
ref.child("users").child("zzV6DQSXUyUkPHgENDbZ9EjXVBj2").observeSingleEvent(of: .value, with: { (snapshot) in
for userSnapshot in snapshot.children.allObjects as! [DataSnapshot] {
let friendsSnapshot = userSnapshot.childrenCount
do {
let frndList = try FirebaseDecoder().decode(Friend.self, from: friendsSnapshot)
self.AppData = [frndList]
print(frndList)
self.tableView.reloadData()
} catch let error {
print(error)
}

Firebase - Nested observeSingleEvent query in for loop callback too many time update

I am facing a serious callback hell on firebase realtime database update.
Situation:
I have comments node which store all the comment's detail information, such as belong to whose userId (uid) , message, and post id (pid). Please see image below.
I have another post-comment nodes, which store comments key under each post id key. Please see image below.
Finally the third nodes is user-comment, which store all comments key under unique user account id key. Please see image below.
Problem:
Everything work fine on "Write comment" function, because it just create a comment key and update comment data to these nodes.
But, when user call "Delete post" function, which will delete all the comments data belong to this post id. Therefore, I have this code logical to loop all the comments data. The whole point is that first I have to get the post-comment snapshot in order to limitation the query amount on comments node (because comments node store all the app user's comment detail data. Without knowing the quantity of comment belong to the target post, it will need to for loop all over the comments node, it is too overload.)
For looping the post-comment will get the commentKey, then I can set Null on comments node and post-comment node.
But the issues happen on I need to use comments node to find out the userId, in order to set NSNull on user-comment. When I calling the event below:
commentsRef.child((snap as AnyObject).key).observeSingleEvent(of:
.value, with: { (commentSnapshot) in
})
The commentsRef callback scope become another thread. Therefore, if I call rootRef.updateChildValues out side of this scope and in the end of for loop (post-comment) which will only update comments node and post-comment node. The user-comment updates data will still assign key:value on the other thread.
updates["user-comment/(userId)/comments/(commentKey)"] = NSNull()
I have to put the rootRef.updateChildValue in the
commentsRef.child((snap as AnyObject).key).observeSingleEvent(of:
.value, with: { (commentSnapshot) in
...
rootRef.updateChildValues(updates)
})
This logic will cause updateChildValues being called too many time if the comments over 10,000 or more than 1 million, because it is in the for looping. I use count down method try to call update only once on the for loop end. But the count number always be 0 in the commentRef scope... I don't know why...
Please help me out with a better solution to dealing with this nested observeSingleEvent update issues without changing the current nodes structure. My goal is to only call rootRef.updateChildValue one time.
Thanks for your help.
Demo code:
func deleteAllCommentsRelateTo(postId: String, callback: ((CommentServiceError?) -> Void)?) {
var error: CommentServiceError?
guard session.isValid else {
error = .authenticationNotFound(message: "Authentication not found.")
callback?(error)
return
}
let uid = session.user.id
let rootRef = Database.database().reference()
let path1 = "posts/\(postId)/comments_count"
let path2 = "posts/\(postId)/uid"
let commentCountRef = rootRef.child(path1)
let authorRef = rootRef.child(path2)
authorRef.observeSingleEvent(of: .value, with: { authorSnapshot in
guard let authorId = authorSnapshot.value as? String else {
error = .failedToDelete(message: "Author not found")
callback?(error)
return
}
if uid != authorId {
error = .failedToDelete(message: "User has no permission to delete this post comments")
callback?(error)
return
}
commentCountRef.runTransactionBlock({ (data) -> TransactionResult in
if let _ = data.value as? Int {
data.value = 0
}
return TransactionResult.success(withValue: data)
}) { (err, committed, snapshot) in
guard err == nil, committed else {
error = .failedToDelete(message: "Unable to delete a comment")
callback?(error)
return
}
var updates: [AnyHashable: Any] = [:]
/**
* [CHECKED] Set NSNull() on comments, post-comment, and user-comment nodes.
*/
let commentsRef = rootRef.child("comments")
let postCommentRef = rootRef.child("post-comment")
let query = postCommentRef.child(postId).child("comments").queryOrderedByKey()
query.observeSingleEvent(of: .value, with: { (data) in
guard data.hasChildren() else {
error = .failedToDelete(message: "No comments data")
callback?(error)
return
}
var count = data.childrenCount
print("post-comment count!!!!!!!: ", data.childrenCount)
for snap in data.children {
guard let commentKeySnap = snap as? DataSnapshot else {
continue
}
count -= 1
let commentKey = commentKeySnap.key
if count == 0 {
print("this is totally not right!!!!!")
}
updates["comments/\(commentKey)"] = NSNull()
updates["post-comment/\(postId)/comments/\(commentKey)"] = NSNull()
commentsRef.child((snap as AnyObject).key).observeSingleEvent(of: .value, with: { (commentSnapshot) in
guard let userId = commentSnapshot.childSnapshot(forPath: "uid").value as? String else {
return
}
updates["user-comment/\(userId)/comments/\(commentKey)"] = NSNull()
print("In this observeSingleEvent will always be 0 count::::: ", count)
if count == 0 {
rootRef.updateChildValues(updates, withCompletionBlock: { err, ref in
guard err == nil else {
error = .failedToDelete(message: "Failed to delete comment")
callback?(error)
return
}
})
print("deleteAllComments: ", updates)
callback?(nil)
}
})
print("count down: ", count)
}
})
})
}
})
}
Solution:
I accidentally found out the correct place to put count -= 1. Originally I put it in the for loop scope, but the count did not decrease in the commentRef in scope. Therefore, I put count -= 1 in the commentRef scope which success count to zero and only call rootRef.update one time.

Storing posts (image, caption) in Firebase database with auto increment Swift 3

I am trying to store user posts in my firebase data like the following:
I have successfully stored each image in storage with the following code:
let storage = FIRStorage.storage()
let data = UIImagePNGRepresentation(postImage!)
// guard for user id
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let photosRef = storage.reference().child("posts")
let imageName = NSUUID().uuidString
let photoRef = photosRef.child("\(uid)")
photoRef.child("\(imageName)").put(data!, metadata: nil){(metaData,error) in
if let error = error {
print("there was an error")
print(error.localizedDescription)
return
}else{
// store downloadURL
let downloadURL = metaData!.downloadURL()!.absoluteString
let values = ["uid": uid, "caption": caption, "download_url": downloadURL]
// store downloadURL at database
let databaseRef = FIRDatabase.database().reference()
// store values in posts/post_1 (post_1..post_2 etc)
}
}
However, I'm having trouble storing the downloadURL (values array) in my posts database because I can't figure out how to have an incremental value for post 1 post 2 post 3 etc etc
Is there a better way to store the posts in the posts database without needing incremental values?
Appreciate any help.
Thanks!
There are a number of ways to store incremental values - it all depends on how you want the values stored and how you will be retrieving and using them.
The general process is to create parent nodes using childByAutoId - Firebase generates the distinct node names for you.
let ref = rootNode.childByAutoid()
ref.setValue("my value")
This will result in nodes with a Firebase generated key
root_node
-Ynaosdokasodkpasd: "my Value"
That being said, it's hard to order them in a meaningful way, so you may want to add nodes that will enable you to specify the order
let ref = rootNode.childByAutoid()
let dict = ["text": "my value", timeStamp: "20161207131600"]
ref.setValue(dict)
which results in
-Ynaosdokasodkpasd
text: "my value"
timeStamp: "20161207131600"
you can then use the timeStamp to order your data in Firebase queries.

Setting a class's values with multiple queries

I've got a class that has Title, users and a timestamp.
My database is set up like :
users
ablksjdlf39493
rooms
room1: true
rooms
room1
timestampOfLastPost: 39403942
users
039403fsjlkj: true
;alkjsdksdkj: true
I'm running a query on my user's rooms and grabbing the snapshot.key to get in this example "room1". Then inside of the first query I run another query and I use that key as a child reference to my rooms node to go to that room (rooms-->room1), and set is as my class object's title. From the 2nd query I'm pulling out the timestamp and use that for my object's timestamp.
I'm a bit confused how to get the childcount of out users.
I was running yet another nested query inside of my second query. But then I was getting all sorts of weird duplicate posts being added to my tableview when I was loading them. And that just doesn't seem very efficient anyways.
Is there a way in my second query to get both the timestamp and the children.count of my users node? The only way I've seen to do that is to run an observer on the specific node and do a snapshot.childrenCount.
edit:
currentUserFirebaseReference.child("rooms").observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
self.interestsArray = []
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
let eachInterest = Interest()
let interestKey = snap.key
let title = snap.key.uppercaseString
eachInterest.title = title
//grabbing the user's current rooms and getting the snap.key to use for my next query.
DataService.ds.REF_INTERESTS.child(interestKey).observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
if let lastuser = snapshot.value!["lastJoin"] as? Int {
eachInterest.latestUser = lastuser
} else {
print("couln't pull that value")
}
//getting the timestamp
DataService.ds.REF_INTERESTS.child(interestKey).child("users").observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
// I can get the users children.count by doing yet another query, but my
// interestsArray gets all messed up and I get duplicates / weird
// loading in my tableview. So I'm looking to not do this whole
// separate query and just grab it from up above where I get the
// timestamp?
let snapshotChildren = String(Int(snapshot.childrenCount))
eachInterest.users = snapshotChildren
self.interestsArray.append(eachInterest)
//self.interestsArray.sortInPlace({ $0.latestUser < $1.latestUser })
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
})
})
}
}
}
}

Resources