I\'m creating a simple chat app to learn Swift and Firebase. I have a query that checks for a chat room\'s messages to load into a tableView. The query returns a snapshot bu
Your dictionary look like :
▿ 1 element
▿ 0 : 2 elements
- key : "-KotqLUUucaRagTRt967"
▿ value : 3 elements
▿ 0 : 2 elements
- key : sender
- value : eGTYRSo81JefgasYLRHUFHUTnEC3
▿ 1 : 2 elements
- key : text
- value : test
▿ 2 : 2 elements
- key : timestamp
- value : 1499914135546
But in fact it should look like (If you want to access to dictionary["sender"]) :
▿ 2 elements
▿ 0 : 2 elements
- key : sender
- value : eGTYRSo81JefgasYLRHUFHUTnEC3
▿ 1 : 2 elements
- key : text
- value : test
▿ 2 : 2 elements
- key : timestamp
- value : 1499914135546
First solution :
You need to add .child("-KotqLUUucaRagTRt967") in your query.
OR
Second solution :
You need to do something like that :
query.observe(.childAdded, with: { snapshot in
for child in snapshot.children {
guard let value = child.value as? NSDictionary else {
return
}
guard let sender = value["sender"] as? String else {
return
}
// You can user the sender
}
})
UPDATE :
query.observe(.childAdded, with: { snapshot in
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
if let value = child.value as? [String:Any], let sender = value["sender"] as? String {
// You can user the sender
}
}
})
NOTE
I replaced observeSingleEvent with observe, as Frank van Puffelen said, it's an uncommon combination for .childAdded.
I do not think you want a ChildAdded handler, so I go with a observeSingleEvent example since you want to query data from the database at that time without triggers. When you use a observeSingleEvent, it is important to keep the database in sync. I would recommend using the code below:
query.keepSynched(true) //keeps data in sync with database, if you have data persistince on in your appDelegate
query.observeSingleEvent(of: .value, with: { (snapshot) in //notice the changed here
print(snapshot)
//Since you want to loop again because there could be multiple
//messages in that chatroom which all have a unique ID, do this loop:
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
//this is 1 single message here
let values = rest.value as? NSDictionary
for (key, value) in values{
print("Key: \(key), value: \(value)")
}
//lets say you want to check if there is a value with a key named "text":
let textUser = values?["text"] as? String ?? "No text found"
//providing a default value if there is no text. You could leave it empty, than it is nil (not recommend)
//Not only is this a lot more readable, I do not use force unwrapping so your app
//cannot cause an exception.
//Get more values from the dictionary as I did with textUser
//Initialize message than here. You will see you do not need to force unwrap anything :D
}
}
})
As Pipiks explained, your issue is that you are attempting to access individual message details at one level higher than the returned data.
The snapshot value is giving you a dictionary of chat messages, where the top level's keys are the chat message keys. To map your returned data to an array of messages, I would use the following code:
query.observeSingleEvent(of: .childAdded, with: { snapshot in
guard let messagesDict = snapshot.value as? [String: AnyObject] else { return }
self.messages = messagesDict.flatMap({ (messageId: String, messageData: Any) -> Message? in
guard
let sender = messageData["sender"] as? String,
let text = messageData["text"] as? String,
let timestamp = messageData["timestamp"] as? Int,
let message = Message(key: messageKey, sender: sender, text: text, timestamp: timestamp) else {
return nil
}
return message
})
self.tableView.reloadData()
})
What this does is map your dictionary of messages to an array of Message
objects.
I have used a flatMap to filter out any messages which do not have sender, text or timestamp values (so the flatMap returns a [Message]
object).
Does that solve the issue?