Output order becomes different in nested query, using Firebase 3, Swift - ios

I'm using Firebase to store user info, and I have this nested function that fetch the post info, and then using the UID in post to fetch the user info.
Nested function to fetch post info and then fetch user
func fetchUser(completion: #escaping (User) -> Void) {
REF_POST.queryOrdered(byChild: "timestamp").observe(.childAdded, with: { (postData) in
let post = ConvertPost(data: postData.key)
print(post.uid) >>>>>>UID ordered by timestamp<<<<<<<<
REF_USER.child(post.uid).observeSingleEvent(of: .value, with: { (userData) in
print(post.uid) >>>>>>UID order becomes different<<<<<<<<
let user = ConvertUser(data: userData)
completion(user)
})
}
I have a print(uid) before observing the users, the output is ordered by timestamp, which is what I want:
PXT6********
WT7i********
WT7i********
PXT6********
And a print(uid) inside observing users, the output order is different:
WT7i********
WT7i********
PXT6********
PXT6********
so my question is why the order becomes different?
I'm calling the method in ViewDidLoad()
Is it something to do with the closure block?
Question Update
After some testing, I found that the output will always group the same uid together, something like A,A,B,B,C,C. Please help me.

Use this code below:
func observeUsers(uid: String, completion: #escaping (User) -> Void) {
print(uid)
REF_USERS.keepSynced(true) // <-- this will make sure your code will update with fresh data
REF_USERS.child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
print(uid)
let user = ConvertUser(data: snapshot.value)
completion(user)
}
})
}
Either use that code, or disable data persistance in your appDelegate. More information:Firebase : What is the difference between setPersistenceEnabled and keepSynced? and in the docs of Firebase ofcourse.

Related

How does control flow work when retrieving Information from Firebase?

var ergebnisBluetezeit = Set<String>()
let refBluetezeit = rootRef.child("Pflanzen").child("Eigenschaften").child("Blütezeit")
refBluetezeit.child("Februar").observeSingleEvent(of: .value, with: { snapshot in
for plant in snapshot.children {
self.ergebnisBluetezeit.insert((plant as AnyObject).value)
}
})
print(ergebnisBluetezeit)
I want to retrieve Data from my Firebase Database. The Retrieving Process does work already, but the following confuses me: the current output from the print is an empty set, but when i use the var ergebnisBluetezeit elsewhere (for example setup a button, which action is to print ergebnisBluetezeit), it is filled. When i put the print in the for loop, it does print the right output, too.
I seem to not have understood the control flow here, so my Question:
How can i use the Set where the print statement is at the moment?
Thanks for your help.
It's the logic of asynchronous calls
print("1") // empty
refBluetezeit.child("Februar").observeSingleEvent(of: .value, with: { snapshot in
print("3") // empty
for plant in snapshot.children {
self.ergebnisBluetezeit.insert((plant as AnyObject).value)
}
print(ergebnisBluetezeit) // not empty
})
print("2") // empty
the value is empty until the request finishes regardless of where in code ordering you run the print , as the numbering above in order 1 , 2 , 3 to know when it finishes you can use completions like
func getData(completion:#escaping() -> ()) {
let refBluetezeit = rootRef.child("Pflanzen").child("Eigenschaften").child("Blütezeit")
refBluetezeit.child("Februar").observeSingleEvent(of: .value, with: { snapshot in
for plant in snapshot.children {
self.ergebnisBluetezeit.insert((plant as AnyObject).value)
}
completion()
})
}
And call
getData {
print(ergebnisBluetezeit)
}

How to safely handle multiple writes in firebase which must all happen

I want to handle a friend request in my app written in Swift using Firebase. In my database, this means that the user sending the request needs to add the other user to their "sentRequests" dictionary, and the user receiving the request needs to add the user sending the requests to their "receivedRequests" dictionary. The problem is, if the user sending the request has a faulty connection and only does the first part, then it might cause issues. Either both writes should happen or none. What can I do to fix this? I included my code below for reference, but honestly if someone just sends me a good tutorial or answer here that would be just has helpful as correctly rewriting my code.
static func sendRequestFromCurrentUser(toUser userThatRequestWasSentTo : User, succeeded : #escaping (Bool)->Void ){
let ref = Database.database().reference().child("users").child(User.current.uid).child("sentRequests").child(userThatRequestWasSentTo.uid)
ref.setValue(userThatRequestWasSentTo.toDictionary(), withCompletionBlock: {(error, ref) in
if error == nil{
let currentUserRef = Database.database().reference().child("users").child(userThatRequestWasSentTo.uid).child("receivedRequests").child(User.current.uid)
currentUserRef.setValue(User.current.toDictionary(), withCompletionBlock: {(error, ref) in
if error == nil{
succeeded(true)
}
else{
succeeded(false)
}
})
}
else{
succeeded(false)
}
})
}
So I stole this from the Firebase blog and got it to match my code. The answer is fairly intuitive, I just hadn't considered it. Basically you just create a reference to the top level of your database and specify the paths you want to write to in the dictionary (so not by creating specific references with child()), and then just call updateChildValues().
static func sendRequestFromCurrentUser(toUser userThatRequestWasSentTo : User, succeeded : #escaping (Bool)->Void ){
let ref = Database.database().reference()
// Create the data we want to update
var updatedUserData : [String : Any] = [:]
updatedUserData["users/\(User.current.uid)/sentRequests/\(userThatRequestWasSentTo.uid)"] = userThatRequestWasSentTo.toDictionary()
updatedUserData["users/\(userThatRequestWasSentTo.uid)/receivedRequests/\(User.current.uid)"] = User.current.toDictionary()
// Do a deep-path update
ref.updateChildValues(updatedUserData, withCompletionBlock: { (error, ref) in
if let error = error {
print("Error updating data: \(error.localizedDescription)")
succeeded(false)
}
else{
succeeded(true)
}
})
}

Firebase Child Added Is Called Every time a Child is Changed

I have a group messaging application that works fine until I want to change some of the basic group properties such as group title, image, etc. Before I show my code to display my conversations and update them I will show you some of my data structure.
When it comes to dealing with the displaying and editing of conversations I use two main nodes. An overall conversation node containing the conversation properties and a conversations node within my current user.
Here is what the conversation node in my current user looks like:
As you can see in the image above my user has a conversation node with a list of conversation ids. These conversation ids refer to a conversation node within my database. Here is a picture of the conversation node:
Just to review the problem. Basically when I update any of the conversation properties (title, image, members) it re calls my child added method which creates an error I will show later.
Here is my code to display the conversations:
func observeUserConversations() {
guard let uid = currentUserProperties.id else {
return
}
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe(.value, with: { (conversationSnapshot) in
if let conversation = Groups(snapshot: conversationSnapshot) {
conversation.groupId = conversationSnapshot.key
self.conversations.append(conversation)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}, withCancel: nil)
}
Here is my code to update some of the conversation properties:
static func updateConversationProperties(conversationId: String, property: String, propertyValue: String) {
let updateConversationPropertyRef = FIRDatabase.database().reference().child("conversations").child(conversationId).child(property)
updateConversationPropertyRef.setValue(propertyValue)
ProgressHUD.showSuccess("Field Updated!")
}
Please note I have tried using update child values instead of set value and it still has the same bug.
To sum up whenever I update a conversation property the child added function is called and appends a duplicate version of the conversation to my conversation array.
I know this may be a bit confusing, so I have a video here showing the bug:
https://youtu.be/OhhnYzQRKi8
In the video above you will see that the same conversaiton is duplicated and added twice.
Any help would be appreciated!
UPDATE
So I changed my observers a bit to look like this:
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observeSingleEvent(of: .value, with: { (conversationSnapshot) in
if let conversation = Groups(snapshot: conversationSnapshot) {
conversation.groupId = conversationSnapshot.key
self.conversations.append(conversation)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}, withCancel: nil)
In the above code, everything works and no duplicates are made. However, now the conversations won't update in realtime. Instead they will display the old data and won't update to the newly changed data. Also if I add a conversation the new added conversaiton won't display.
Here is what I notice:
The way you had the code originally, the second listener was triggered any time a change is made to the value of /"conversations"/snapshot.key. And whenever this call was made, you were appending the conversationSnapshot to conversations array:
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe(of: .value, with: { (conversationSnapshot) in
if let conversation = Groups(snapshot: conversationSnapshot) {
conversation.groupId = conversationSnapshot.key
self.conversations.append(conversation) // here is where you are appending the data. This will be appended each time a change is made
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}, withCancel: nil)
Now as you point out, if you change FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe to .observeSingleEvent, the data won't append again, but you won't get updates. One option is whenever the listener is triggered, you search the array for the snapshot key, and then update the snapshot at that index if found. Not the most efficient method, to be sure.
In summation, it sounds like you do need to use observe, but as it stands, the data is duplicated because the code appends the snapshot to the end of the array whenever a change is made to the snapshot's value. You will have to use something other than self.conversations.append(conversation).
I'd be happy to brainstorm some other options if you wanted to message me directly.
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").observe(.childAdded, with: { (conversationAdded) in
if conversationAdded.key == snapshot.key {
if let group = Groups(snapshot: conversationAdded) {
self.conversations.append(group)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}
})
}, withCancel: nil)
FIRDatabase.database().reference().child("users").child(uid).child("conversations").observe(.childAdded, with: { (snapshot) in
FIRDatabase.database().reference().child("conversations").child(snapshot.key).observe(.childChanged, with: { (conversationSnapshot) in
let conversationIdsArray = self.conversations.map({$0.groupId})
let changeAtGroupIdIndex = conversationIdsArray.index(of: snapshot.key)
let conversationToBeUpdated = self.conversations[changeAtGroupIdIndex!]
conversationToBeUpdated.setValue(conversationSnapshot.value, forKeyPath: conversationSnapshot.key)
self.tableView.reloadData()
}, withCancel: nil)
}, withCancel: nil)
In the above code, I create two different observers. The first one loads conversations when the app is loaded or a conversation is added. The second one updates the conversation array if the child has been changed. This solves both problems.
Filter array with data excluding the object just received. Identify that object in the existing array by a unique id like groupID or chatID in my case. Then the repeated object will be removed
self.conversations = self.conversations.filter { obj in (obj.chatId as? String) != (data.chatId as? String) }
self.conversations.append(data)

Showing post from currentUsers wall - Firebase / Swift

I am trying to create a wall/timeline that shows posts from all the users that currentUser is following. All users that currentUser is following is showed under Users -> UserID -> Following. Whenever one of their followers is making a post it will be added under feed-items with an autoID - the key (the autoID) is added to currentUsers Users -> UserID -> Wall at the same time.
Here is an image of an example from my Firebase database:
Under Wall as you can see, one of this users followers has made a post (the whole post is saved under feed-items) and the autoID of that post has made it to the users Wall.
Now I am trying to figure out how to show all the posts in feed-items, based on the autoID's stored under currentUsers Wall.
I have tried the following code, but nothing shows and when it reaches this line print(self.updates.count) it is printing 0.
func startObersvingDB(userID: String) {
FIRDatabase.database().reference().child("Users").child(userID).child("Wall").observeEventType(.ChildAdded, withBlock: { snapshot in
if let posts = snapshot.value!["Post"] as? String {
self.postArray.append(posts)
for i in 0..<self.postArray.count {
let post = self.postArray[i]
print(post)
FIRDatabase.database().reference().child("feed-items").queryEqualToValue(post).observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newUpdates = [Sweet]()
for update in snapshot.children {
let updateObject = Sweet(snapshot: update as! FIRDataSnapshot)
newUpdates.append(updateObject)
}
self.updates = newUpdates.reverse()
print(self.updates.count)
self.tableView.reloadData()
}) { (error: NSError) in
print(error.description)
}
}
}
})
}
If I am guessing right, your structure for feed-items is something like this.
feed-items
-UniquePostID
-Post Data (key-value pair(s))
If this is the case then to retrieve data for a post use .child(post) instead of .queryEqualToValue(post). Also since this will return DataSnapshot for single post you can directly create your Sweet object and append it in existing updates array.
One more thing I don't think you need to iterate entire postArray each time a new post is added. You should retrieve data for new post only.
Hope this helps!!

How to retrieve data synchronously from Firebase?

I have two collections namely, Users and Questions.
Based on the user logged in using userId, I retrieve the currQuestion value from users collection.
Based on the currQuestion value, I need to retrieve the question document from Firebase Questions collection.
I've used the below code to retrieve userId
rootRef.child("0").child("users")
.queryOrderedByChild("userId")
.queryEqualToValue("578ab1a0e9c2389b23a0e870")
.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children {
self.currQuestion = child.value["currentQuestion"] as! Int
}
print("Current Question is \(self.currQuestion)")
//print(snapshot.value as! Array<AnyObject>)
}, withCancelBlock : { error in
print(error.description)
})
and to retrieve question
rootRef.child("0").child("questions")
.queryOrderedByChild("id")
.queryEqualToValue(currQuestion)
.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children {
print(child.value["question"] as! String)
}
}, withCancelBlock: { error in
print(error.description)
})
But the above code executes asynchronously. I need to solution to make this synchronous or how to implement listeners so I can fire back the question query once the currQuestion value is changed?
Write your own method which takes in a completion handler as its parameter and waits for that block of code to finish. Like so:
func someMethod(completion: (Bool) -> ()){
rootRef.child("0").child("users")
.queryOrderedByChild("userId")
.queryEqualToValue("578ab1a0e9c2389b23a0e870")
.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children {
self.currQuestion = child.value["currentQuestion"] as! Int
}
print("Current Question is \(self.currQuestion)")
completion(true)
//print(snapshot.value as! Array<AnyObject>)
}, withCancelBlock : { error in
print(error.description)
})
}
And then whenever you want to call that function, call like so:
someMethod{ success in
if success{
//Here currValue is updated. Do what you want.
}
else{
//It is not updated and some error occurred. Do what you want.
}
}
Completion handlers are usually used to wait for a block of code to finish executing completely. P.S. As long as they don't block the main thread, asynchronous requests are made to act synchronous by adding a completion handler like the code shown above.
What it simply does is wait for your currValue to be updated first (receiving the data async from the server) and then when you call someMethod like how I've shown, and since the last and only parameter to the function someMethod is a closure (a.k.a, trailing Closure ), you can skip the parenthesis and call it. Here is a good read about closures. And since the closure is of type (Bool) -> (), you just tell your someMethod when the task is completed which is done like completion(true) in my code, and then while calling it, you call it with success (You can use any word you want) which WILL BE of type Bool as it is declared like so, And then use it in the function call. Hope it helps. :)

Resources