MJQZ1347 MJQZ1347 - 5 months ago 69
iOS Question

Firebase / iOS: runTransactions sometimes doesn't work

I am working on a chat app, where users should get notified about new messages from their contacts. This notification message should also include the number of unread messages. Because both the sender and receiver can update this information

runTransaction
is preferred. Unfortunately sometimes it doesn't work. It feels "stuck" and then starts working after a while again. The
privateChats
node (see below) gets always updated with the latest message, but not the
openChatMessages
node.

Can this happen if many messages are sent in a short period of time, i.e.
runTransactions
is performed too often for the same
ref
?

My data structure:

privateChats
$userId
$chatId
$messageId
text
timestamp
senderId
senderEmail
senderName

// this node contains information about open chats
// like last message and counter for unread messages
openChatMessages
$userId
$chatId
text
timestamp
senderId
senderEmail
senderName
counter


My code:

class ChatViewController: JSQMessagesViewController {

var user: FIRUser!
var ref: FIRDatabaseReference!
var chatRef: FIRDatabaseReference!
var senderOpenChatRef: FIRDatabaseReference!
var receiverOpenChatRef: FIRDatabaseReference!

// the following variables will be set before ChatViewController appears

var chatId: String?
var receivId: String?
var receiverEmail: String?
var receiverName: String?

override func viewDidLoad() {
super.viewDidLoad()
self.user = FIRAuth.auth()?.currentUser!
self.ref = FIRDatabase.database().reference()
self.chatRef = self.ref.child("privateChats").child(self.user.uid).child(self.chatId!)
self.senderOpenChatRef = self.ref.child("openChatMessages").child(self.user.uid).child(self.chatId!)
self.receiverOpenChatRef = self.ref.child("openChatMessages").child(self.receiverId!).child(self.chatId!)
}

func sendMessage(text: String) {
var messageObject = [String: AnyObject]()
messageObject["text"] = text
messageObject["timestamp"] = FIRServerValue.timestamp()
messageObject["senderEmail"] = self.user.email
messageObject["senderName"] = self.user.displayName
messageObject["senderId"] = self.user.uid

let messageId = self.ref.child("privateChats").child(self.user.uid).child(self.chatId!).childByAutoId().key

let childUpdates = [
"/privateChats/\(self.user.uid)/\(self.chatId!)/\(messageId)": messageObject,
"/privateChats/\(self.receiverId!)/\(self.chatId!)/\(messageId)": messageObject
]

self.ref.updateChildValues(childUpdates, withCompletionBlock: { (error, ref) -> Void in
if error != nil {
print("childUpdates error:\(error)")
return
}

JSQSystemSoundPlayer.jsq_playMessageSentSound()
self.finishSendingMessage()
self.updateOpenChats(text)
})
}


func updateOpenChats(text: String) {

// update the receivers openChatObject with increasing the counter
self.receiverOpenChatRef.runTransactionBlock({ (currentData: FIRMutableData) -> FIRTransactionResult in

var openChatObject = [String: AnyObject]()

// update openChatObject with the latest information from currentData
if currentData.hasChildren() {
openChatObject = currentData.value as! [String: AnyObject]
}

openChatObject["text"] = text
openChatObject["timestamp"] = FIRServerValue.timestamp()
openChatObject["senderEmail"] = self.user.email
openChatObject["senderName"] = self.user.displayName
openChatObject["senderId"] = self.user.uid

var counter = openChatObject["counter"] as? Int
if counter == nil {
counter = 1
} else {
counter = counter! + 1
}
openChatObject["counter"] = counter

currentData.value = openChatObject
return FIRTransactionResult.successWithValue(currentData)
}) { (error, committed, snapshot) in
if let error = error {
print("updateOpenChats: \(error.localizedDescription)")
}
}

// update your (the sender's) openChatObject with setting the counter to zero
self.senderOpenChatRef.runTransactionBlock({ (currentData: FIRMutableData) -> FIRTransactionResult in

var openChatObject = [String: AnyObject]()

// update openChatObject with the latest information from currentData
if currentData.hasChildren() {
openChatObject = currentData.value as! [String: AnyObject]
}

openChatObject["text"] = text
openChatObject["timestamp"] = FIRServerValue.timestamp()
openChatObject["senderEmail"] = self.receiverEmail
openChatObject["senderName"] = self.receiverName
openChatObject["senderId"] = self.receiverId
openChatObject["counter"] = 0

currentData.value = openChatObject
return FIRTransactionResult.successWithValue(currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
}
}
}

Answer

Ok, apparently there is a bug in the Firebase SDK. The callback of updateChildValues doesn't get executed sometimes, even though the update was successful. I removed the completionBlock and now it works flawlessly.

  self.ref.updateChildValues(childUpdates) 
  JSQSystemSoundPlayer.jsq_playMessageSentSound()
  self.finishSendingMessage()
  self.updateOpenChats(text)
Comments