Swift 4 & Firebase message being duplicated when they are received - ios

For some reason when the user is receiving a message, the past messages are being duplicated. It's weird because the user who sends the messages are not having their messages duplicated when they send the message. Any help with my code would be much appreciated.
var messages = [Message]()
fileprivate func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let userId = user?.uid else { return }
if currentKey == nil {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observe(.value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
})
self.currentKey = first.key
}
} else {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 10).observe(.value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
if snapshot.key != self.currentKey {
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
}
})
self.currentKey = first.key
}
}
}
The only places observeMessages() is being called is in willDisplay as shown below and in viewDidLoad
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if messages.count > 2 {
if indexPath.item == messages.count - 1 {
observeMessages()
}
}
}
The only difference between the user sending and user receiving message is when the user sends a message and the submit message button is pressed, I call self.messages.removeAll() and then call self.observeMessages() again which seems to work because the user who sends the message doesn't have duplicate messages.

you have used .value , instead you should use childAdded i think, then it will only get the latest added child and not the previous ones.
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observe(.childAdded) { (snapshot) in

Related

retrieve chat message in firebase issue in ios swift

I am using firebase for chat app.when I try to send a message. than message, table got value but not update chat-message table. I refer to this link Here [https://github.com/DevSurya/ChatApp-Swift-And-Firebase].
when I run demo its working fine. Observe method is not calling in my case and collectionview is not refreshing
This is how I write data :
private func sendMessageWithProperty(_ property: [String: AnyObject]){
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = user!.id!
let fromId = Auth.auth().currentUser!.uid
let timeStamp = NSNumber.init(value: Date().timeIntervalSince1970)
var values: [String : AnyObject] = ["toId":toId as AnyObject, "fromId":fromId as AnyObject, "timeStamp":timeStamp]
values = values.merged(with: property)
childRef.updateChildValues(values)
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error!)
return
}
self.inputTextField.text = nil
let userMessageRef = Database.database().reference().child("user-messages").child(fromId).child(toId)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId: 1])
let recipentUserMessageRef = Database.database().reference().child("user-messages").child(toId).child(fromId)
recipentUserMessageRef.updateChildValues([messageId: 1])
}
}
This is how I read data
func observeMessage() {
guard let uid = Auth.auth().currentUser?.uid, let toId = user?.id else {
return
}
let userMessageRef = Database.database().reference().child("user-messages").child(uid).child(toId)
userMessageRef.observe(.childAdded, with: { (snapshot) in
let messageId = snapshot.key
let messagesRef = Database.database().reference().child("messages").child(messageId)
messagesRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: AnyObject] else {
return
}
let message = Message()
message.setValuesForKeys(dict)
self.messages.append(message)
DispatchQueue.main.async {
self.collectionView?.reloadData()
let indexpath = NSIndexPath.init(item: self.messages.count-1, section: 0)
self.collectionView?.scrollToItem(at: indexpath as IndexPath, at: .bottom, animated: true)
}
}, withCancel: nil)
}, withCancel: nil)
}
The ObserveMessage() function get automatically called when there is an update in database.but in my case its not get called. I think the problem is with user-messages table: it is not created when I send message.

Load new messages Swift 4.2 & Firebase

I have created a messaging system for my app and am paginating the messages within the chat log but I'm having an issue that if a new message is sent the user will have to leave the screen and re open the controller to view the new messages they have sent/received. I have tried to reload the collection view and observe the messages again with no luck. Any help is appreciated.
Observing the messages. With Pagination. (working great! On initial load.)
var messages = [Message]()
fileprivate func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let userId = user?.uid else { return }
if currentKey == nil {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
})
self.currentKey = first.key
}
} else {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 4).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
if snapshot.key != self.currentKey {
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
}
})
self.currentKey = first.key
}
}
}
From Firebase database documentation
In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the observeSingleEventOfType method to simplify this scenario: the event callback added triggers once and then does not trigger again.
I suggest you to change to observeEventType:withBlock whichs allow you to observe all changes events.
Hope this helps.
The way I set mine up was to call the function in viewDidLoad and then again in viewDidAppear. I'm still learning as well, but you may want to try that, it would probably look something like this:
override func viewDidLoad() {
super.viewDidLoad()
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}
And again in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}

iOS Firebase asynchronous data retrieval into Array

I try to retrieve data from Firebase into Array. Because it runs asynchronously, the results that I want to show in my CollectionView is a delay until I switch back and forth. I am very new to asynchronous functions in iOS. Please help me to complete my code.
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
}
})
You need to reload the collection after you retrieve the data so after the for loop call reloadData()
for child in result {
}
self.collectionView.reloadData()
//
func getValueFromDatabase(completion: #escaping (_ status: Bool) -> Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion(true)
}
else {
completion(false)
}
})
}
//
self.getValueFromDatabase { (status) in
if status {
// success
}
}
I'm working with Firebase in my project right now. I would suggest the following solution: wrap the database observer in a distinct function which gets completion block as a parameter.
func getValueFromDatabase(completion: ()->Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion()
}
})
}
This way you can call the function from anywhere providing the desired action after fetching data from db is finished:
getValueFromDatabase(completion:{
self.collectionView.reloadData() //or any other action you want to fulfil
})

How do I add a google maps marker when Pressing a button?

Is it possible to fetch all child nodes from parent in firebase db?
Trying to say this:
I want to get all posts at the same time.
It fetches videos but only the current users videos. I want to fetch ALL users videos at the same time.
Here's some code to get more understanding of what I'm doing:
fileprivate func fetchAllPost() {
fetchPosts()
fetchAllPostsFromUserIds()
}
fileprivate func fetchAllPostsFromUserIds() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("posts").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
FIRDatabase.fetchUserWithUid(uid: key, completion: { (user) in
self.fetchPostsWithUser(user: user)
})
})
}) { (err) in
print("failed to fetch following users ids:", err)
}
}
var posts = [Post]()
fileprivate func fetchPosts() {
guard let currentUserID = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.fetchUserWithUid(uid: currentUserID) { (user) in
self.fetchPostsWithUser(user: user)
}
}
fileprivate func fetchPostsWithUser(user: User) {
let ref = FIRDatabase.database().reference().child("posts/\(user.uid)/")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
guard let dictionaries = snapshot.value as? [String: Any] else { return }
dictionaries.forEach({ (key,value) in
guard let dictionary = value as? [String: Any] else { return }
var post = Post(user: user, dictionary: dictionary)
post.id = key
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("likes").child(key).child(uid).observe(.value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
self.posts.append(post)
self.posts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
}, withCancel: { (err) in
print("Failed to fetch info for post")
})
print(self.posts)
})
}) { (error) in
print("Failed to fetch posts", error)
}
}
I don't know Swift, but you could fetch FIRDatabase.database().reference().child("posts") and then iterate over children.

update Firebase deleted table cell

I have a node in Firebase database. its a node for the messaging portion in my App. It is a private chat. I want to incorporate the ability for one user to delete his messages but the messages still remain in the other user's app. so I created a fan out property.
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let convoRef = FIRDatabase.database().reference().child("messages").child(convoId!)
let itemRef = rootRef.child("messages").child(convoId!).childByAutoId() // 1
let messageItem = [ // 2
"senderId": senderId!,
"ReceiverId": senderDisplayName!,
"text": text!,
"timestamp": NSDate().timeIntervalSince1970,
"convoId": convoId!
] as [String : Any]
// itemRef.setValue(messageItem) // 3
itemRef.updateChildValues(messageItem) { (error, convoRef) in
if error != nil{
print(error)
return
}
let userMessagesRef = FIRDatabase.database().reference().child("user-messages").child(self.senderId!)
let messageId = itemRef.key
userMessagesRef.updateChildValues([messageId: 1])
let userMessagesRefs = FIRDatabase.database().reference().child("user-messages").child(self.senderDisplayName!)
let messagedId = itemRef.key
userMessagesRefs.updateChildValues([messagedId: 1])
}
}
And then i created the option to delete the message cell.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let ref = FIRDatabase.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
FIRDatabase.database().reference().child("user-messages").child(uid).child( self.messageId!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}else{
}
})})
self.messagesDictionary.removeValue(forKey: uid)
self.handleReloadTable()
}
But when the table reloads,it crashes
func loadData()
{
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let ref = FIRDatabase.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
})
FIRDatabase.database().reference().child("messages").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let convoId = info["convoId"]
let toId = info["ReceiverId"] as! String!
let fromId = info["senderId"] as! String!
if (toId == self.loggedInUserUid || fromId == self.loggedInUserUid) {
let ref = FIRDatabase.database().reference().child("messages").child(convoId as! String).child(self.messageId!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message(dictionary: dictionary)
// self.messages.append(message)
if let receiver = message.convoId {
self.messagesDictionary[receiver] = message
self.messages = Array(self.messagesDictionary.values)
print(self.messages)
self.messages.sort(by: { (message1, message2) -> Bool in
return (message1.timestamp?.int32Value)! > (message2.timestamp?.int32Value)!
})
}
//this will crash because of background thread, so lets call this on dispatch_async main thread
DispatchQueue.main.async(execute: {
self.MessageTableView.reloadData()
})
}
}, withCancel: nil)}
}
}}})
}
And says messageId is an optional value.

Resources