Simultaneous transactions in Firebase Realtime Database are failing to get most recent data (Swift) - swift

I'm trying to make multiple updates to my firebase realtime database at the same time using transactions, but when I do this the calls aren't waiting for one another, and instead I only get one update instead of the seven I should be getting.
Below is a segment of my code that is called 7 times in a for loop:
global.newGame.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if let post = currentData.value as? NSDictionary {
let player0Info = post["player0Info"] as? NSDictionary
var serverHand = player0Info!["Hand"] as? Array<Int>
serverHand?.append(2)
print("The value of serverHand is \(serverHand ?? [-1])")
global.newGame.child("player0Info/Hand").setValue(serverHand)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
}
And the output it gives is
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
Is there a way to prevent this from happening? Why doesn't putting this inside of a transaction keep it from being simultaneously changed by multiple calls?
As a side note, everything works as expected when I make these calls in spread-out intervals, I only get this issue from overlapping transactions.

I'm not an iOS dev, so expect typos/syntax errors.
From what I can tell, the way you handle the transaction is incorrect. When using a transaction, you ask for the "latest" data (see note), mutate that data, then send it back. However, in your code you are getting the data, mutating it and then at the same time, both overriding it using setValue and sending the changed data back. Because you are using setValue here, you create a loose infinite loop - where sometimes the transaction finishes first (everything is okay) and sometimes the setValue finishes first (the transaction is tried again).
A transaction is not a one-time operation, if the data at the location you are using is modified, the transaction can be retried. In this case, the call to setValue can trigger one of these retries.
If serverHand.append() mutates the array (i.e. you don't need to also add player0Info!["Hand"] = serverHand.append()), then simply removing the setValue should fix your problem.
Lastly, as pointed out by #Jay in this answer's comments, you need to also set currentData.value with the new data to commit any changes.
global.newGame.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if let post = currentData.value as? NSDictionary {
let player0Info = post["player0Info"] as? NSDictionary
var serverHand = player0Info!["Hand"] as? Array<Int>
serverHand?.append(2)
print("The value of serverHand is \(serverHand ?? [-1])")
// note that the setValue was removed
currentData.value = post; // link new data for updating
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
}
Note: I used "latest" in quotes because the first time a transaction is tried, the locally cached data is used to process the transaction. If the server's data is different from what's cached locally, the transaction will be processed a second time.
Edit 1: Added #Jay's contributions and removed brief references to the log messages being abnormal, as if called in a loop, multiple logs are to be expected.

Related

Firebase removeObserver is not working in Swift 5

I am using Firebase's Realtime database in my app. I am fetching data from the database and do some change and after that I am removing the observer which is not working fine.
I have some data in Realtime Database like this:
I am using firebase's observe(.value) function to get this value and after that I am updating an entry and then I am removing the observer. This is my code:
func updatePoints() {
let firebaseId = UserDefaults.standard.value(forKey: "firebaseId") as? String ?? ""
let reference = self.database.child("Points").child(firebaseId)
var handler : UInt = 0
handler = reference.observe(.value, with: { snapshot in
guard let userPoints = snapshot.value as? [String : Any] else {
print("no points data found")
return
}
let pointsLeft = userPoints["points_left"] as? Int ?? 0
reference.child("points_left").setValue(pointsLeft - 1)
reference.removeObserver(withHandle: handler)
})
}
The problem now is, this observer runs twice. For example, if "points_left" : 10, then after this function the points left will have 8 value but it should have 9 instead. It is running twice and I am not understanding why is it doing so as I am using removeObserver. Can someone help me with this?
The reason to the above unexpected behaviour is the setValue function you called to update the points is triggering another .value event in the database. Then it triggers the observer again. Therefore, by the time you remove the observer, it has already triggered twice. This leads to decrease of points by 2 instead of 1.
So if u interchange the last two lines, by the time you call the setValue function observer is removed. So it will not get triggered for the second time.

Reading child value from firebase database in swift

I am trying to read a child value so I can add 1 to it then update the value, but my code to read the value is giving me a SIGABRT error and crashing, what is wrong with it? REF_FEEDMESSAGES is a reference to the database that holds all the messages
var stringLikes = REF_FEEDMESSAGES.child(key).value(forKey: "likes") as! String ?? "0"
You need ( observe / observeSingleEvent is up to you according to the logic of your app )
REF_FEEDMESSAGES.child("\(key)/likes").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
}) { (error) in
print(error.localizedDescription)
}
Using this .value(forKey: "likes") as! String for sure will crash the app , as it's not a local dictionary to query it's data synchronously

Data failed to be returned from firebase [swift]

Firebase not returning data for a given record, despite it being present in the db .
I have some data in firebase that looks like this (2 records to be exact), and conforms to this structure
{
users : [
SOmeBig0lDID:{
credentials:{
name:"bob"
number:"+10778727737"
}
}
..... other users
]
}
In my app i am iterating over the phone numbers in my contact list and issuing a search for each number to identify which is a user. When i issue the search, i get back a value for one of my records, but not the other.
In terms of implementation, as you can see below, I am waiting for a search to complete before issuing another one , here is my code. Im wondering if it is just a corrupted record, in which case this is also very worrying.
I am also 100% sure that the phone number formatting is correct when the query is submitted to match that stored in firebase.
//get a list of correctly formatted phone numbers up here
func getuser(number:String,count:Int,total:Int){
print("Looking for :" + number+"|")
let usersRef = Database.database().reference().child("users")
let query = usersRef.queryOrdered(byChild: "credentials/number").queryEqual(toValue: number)
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
var dict = snap.value as! [String: Any]
let creds = dict["credentials"]!
print(creds)
}
if count < total-1 {
getuser(number:finalNumbers[count+1], count:count+1, total:finalNumbers.count)
}
})
}
getuser(number:finalNumbers[0], count:0, total:finalNumbers.count)
I am expecting to get back the record for a number that i am querying , but i do not. I have verified that the number i am searching for is the number that is in the realtime database and have gone so far as to change the number in the realtime db to match exactly what i see the client issuing (it was doing this already however for completeness i wanted to ruled out the client doing some odd encoding) and yet still the record is not returned.
Oddly if i set the number of the record i am not getting returned, to the number of the record i am getting returned , i get both back. (both number fields have the same value).
If this is an issue with firebase more generally that others have experienced , please comment, i don't want to be using somethign that just doesn't work consistently.
any thoughts let me know, been stuck on this issue for a while.

runTransactionBlock currentData is null

I have this function trying to run transaction to update Firebase value:
if opponentPoints < self.points {
print("the host won")
refPoints.runTransactionBlock({ (currentData:FIRMutableData) -> FIRTransactionResult in
print("currentData", currentData.value!)
if var pointsToUpdate = currentData.value as? [String : Any] {
var pointsUp = pointsToUpdate["points"] as! Int
pointsUp += 3
pointsToUpdate["points"] = pointsUp
currentData.value = pointsToUpdate
return FIRTransactionResult.success(withValue: currentData)
}
return FIRTransactionResult.success(withValue: currentData)
})
and I have this structure:
users
uid
points
name
etc.
The problem is that when I try to print currentData, I get null as result.
What am I doing wrong?
When you start a transaction, you transaction handlers is immediately invoked with the client's best guess for the current value of the data. This will most often be null.
The Firebase client then send the best guess (null) and your value to the server. The server checks, realizes that the current value is wrong and rejects the change. In that rejection, it then tells the client the current value of the location.
The Firebase client then invokes your transaction handler again, but now with the new best guess to the current value.
Long story short: your transaction handler needs to be prepared to receive null as the current value.

How can my code know when Firebase has finished retrieving data?

I just want to ask about firebase retrieve data. How can i handle firebase retrieve data finished? I don't see any completion handler.
I want to call some function after this firebase data retrieve finished. How can i handle???
DataService.ds.POST_REF.queryOrderedByChild("created_at").observeEventType(.ChildAdded, withBlock: { snapshot in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let postKey = snapshot.key
let post = Post(postKey: postKey, dictionary: postDict)
self.posts.append(post)
}
})
In Firebase, there isn't really a concept of 'finished' (when listening to 'child added'). It is just a stream of data (imagine someone adds a new record before the initial data is 'finished'). You can use the 'value' event to get an entire object, but that won't give you new records as they're added like 'child added' does.
If, you really need to use child added and get notified when it's probably finished, you can set a timer. I don't know swift, but here's the logic.
Set up your 'child added' event.
Set a timer to call some finishedLoading() function in 500ms.
Each time the 'child added' event is triggered, destroy the timer set in step two and create another one (that is, extend it another 500ms).
When new data stops coming in, the timer will stop being extended and finsihedLoading() will be called 500ms later.
500ms is just a made up number, use whatever suits.
Do one request for SingleEventOfType(.Value). This will give you all info initially in one shot, allowing you to then do whatever function you want to complete once you have that data.
You can create a separate query for childAdded and then do anything there you want to do when a new post has been added
Write your entire block of code in a function which has a completion handler like so:
func aMethod(completion: (Bool) -> ()){
DataService.ds.POST_REF.queryOrderedByChild("created_at").observeEventType(.ChildAdded, withBlock: { snapshot in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let postKey = snapshot.key
let post = Post(postKey: postKey, dictionary: postDict)
self.posts.append(post)
}
completion(true)
})
}
Then call it somewhere like so:
aMethod { success in
guard success == true else {
//Do something if some error occured while retreiving data from firebase
return
}
//Do something if everything went well.
.
.
.