I have a chat app. A user can choose another user in the app to chat with. Once a user sends a message to another user, their appended messages is displayed in a table view controller and when you click on the table view cell you are segued into a detailed controller.
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
JSQSystemSoundPlayer.jsq_playMessageSentSound() // 4
finishSendingMessage() // 5
isTyping = false
}
So only the two users chatting can view the messages. But I want one user to be able to delete the table view cell in their app (the messages) but when they do that it deletes in Firebase and that means it deletes in the other user's app too but I want it to only delete in the user that deleted the message. Here is how the messages are appended to be displayed in the tableview controller
func loadData()
{
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)
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message(dictionary: dictionary)
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)}
}
}}})
}
If an update is made to a Firebase Realtime Database node, then any user reading on that node would see the same value ( if the security rules allow the user to read that node ). So, to answer your question - you can't really make user specific updates to a database path.
Having said that, in your case, you can do one of the following :-
You can delete what you're trying to delete on the client side, so that it won't be visible any longer. However, that won't delete the data in the database, and you would see it again the next time you download from the same path.
You can have separate conversation paths for both the users, and store the same messages in both these locations instead of a common one. In this method, you'd be duplicating data. But if you want to make user-specific changes, then this would more than do it for you.
Related
I have an app where I have a list of data that I manually enter into Firebase. Here is an image of the Firebase Database:
When the user selects an index row, it goes into another view controller. Inside that view controller there is a tableview, which I want to populate with the data from the “name” that I selected.
For example, if I select the “name” from [acct0], I want the “name” and “genre” to also appear in the new tableview.
Currently, I am trying to print the data based on the selection in the previous view controller.
let user = User()
ref?.child("FeaturedAccounts/AccountNames").child(user.name!).observeSingleEvent(of: .value, with: { (snapshot) in
let userDict = snapshot.value as! [String: Any]
let genre = userDict["genre"] as! String
print("Genre: \(genre)")
print(user.name!)
})
Your query looks correct to me. It's hard to say exactly what is going wrong without more information. Here is some code you can use to help debug your issue. If you post some more of your code or explain exactly what is currently happening with your query I can help more.
let user = User()
let ref = Database.database().reference()
ref.child("FeaturedAccounts").child("AccountNames").child(user.name!).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print(snapshot.value)
}
else {
print("Query returned no data")
}
if let userDict = snapshot.value as? [String: AnyObject] {
if let genre = userDict["genre"] as? String {
print("Genre: \(genre)")
}
else {
print("could not cast genre to string")
}
}
else {
print("Could not cast userDict")
}
})
I have a view controller with three text fields. When the user uses the app for the first time, they enter some information in those text fields and press done, then the information goes to Firebase database:
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).setValue(saveObject)
However, the user also has the ability to edit those three text fields. As of now, when the user edits one text field and clicks done. The information of the non-edited text fields appear in their own text fields and then the user has to click the done button again for the edited text field to be sent to Firebase:
if City.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let city = postsDictionary["City"] as? String ?? ""
self.City.text = city
}})
}
if Major.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let major = postsDictionary["Major"] as? String ?? ""
self.Major.text = major
}})
}
if College.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let college = postsDictionary["College"] as? String ?? ""
self.College.text = college
}})
}
if College.text=="" || Major.text=="" || City.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
})
}else{
if let City = City.text{
if let Major = Major.text{
if let College = College.text{
let saveObject: Dictionary<String, Any> = [
"uid": uid,
"City" : City,
"Major" : Major,
"College" : College
]
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).setValue(saveObject)
I don't think this is very user-friendly, I would like the user to edit whatever text field they want and I do not want the non-edited text field values to appear in their text fields and I would like the user to click on the done button once for the newly edited text field value to be sent to firebase.
Just create this func in viewdidload. This will load your current properties in firebase. You should adopt the UITextFieldDelegate and add the function textDidBegin which then you can make the textField (sender as! textField class) blank instead of having these loaded properties.
func reloadFirDatabase()
{ FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let city = postsDictionary["City"] as? String ?? ""
let major = postsDictionary["Major"] as? String ?? ""
let college = postsDictionary["College"] as? String ?? ""
if City.text.isEmpty {
self.City.text = city
{
if Major.text.isEmpty {
self.Major.text = major
}
if College.text.isEmpty {
self.College.text = college
}
}})
}
When you press done you should still load this in:
if let City = City.text{
if let Major = Major.text{
if let College = College.text{
let saveObject: Dictionary<String, Any> = [
"uid": uid,
"City" : City,
"Major" : Major,
"College" : College
]
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).setValue(saveObject)
I can guarantee you that doing it this way you only need to press the done button once.
I'm trying to load in different values for each cell in a tableview. Currently, I load in a teamID, display it on the current cell, then use that ID to load in the other attributes of the team.
self.ref?.child("Teams").child(currentTeamID).child("Number").observeSingleEvent(of: .value, with: { (snapshot) in
let number1 = snapshot.value as? Int
if let teamNum = number1 {
Cell.teamNumber.text = "team " + String(teamNum)
//breakpoint
}
})
self.ref?.child("Teams").child(currentTeamID).child("memberCount").observeSingleEvent(of: .value, with: { (snapshot) in
let memcon = snapshot.value as? Int
if let membercount = memcon {
Cell.userCount.text = "Members: " + String(membercount)
//breakpoint
}
})
return Cell
My issues comes when trying to load in these other attributes. Should I be doing this is a different way? Right now it loads only the second .observeSingleEvent I have tried placing breakpoint where I indicated above, but only the second one ever gets hit. Do I need a separate reference or is there a way to load all the values from a parent object?
Thanks a whole bunch.
Added Firebase Structure:
ftc-scouting-app
Teams
Brophy Robotics
Name: "Brophy Robotics"
Number: "201"
Password: "bronco"
memberCount: 2
memberList
member1: "5ilQc8KlrERLAmtFXjWaOZLIcoC3"
member2: "syV9SS6S9hY8PyKBOC0VQ3NNv0v2"
Users
5ilQc8KlrERLAmtFXjWaOZLIcoC3
(User Info Values)
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
(User Info Values
The values I'm trying to load are the team number and the member count. I want to put them on the cell as it loads in each team that each user has. So, I just need it to load each value and put it on my custom table view cell that has all the fields for it. To clarify - I already know that it retrieves the team ID properly because it is able to put it on the cell.
The value currentTeamID is a value that I have already loaded in, and is the id (which is the same as the name) of the current cell's prospective team.
First, change the structure
ftc-scouting-app
Teams
Jyis9009kos0kslk //should be generated with childByAutoId()
Name: "Brophy Robotics"
Number: "201"
Password: "bronco"
memberCount: "2"
memberList:
5ilQc8KlrERLAmtFXjWaOZLIcoC3: true //uid as the key
syV9SS6S9hY8PyKBOC0VQ3NNv0v2: true
Users
5ilQc8KlrERLAmtFXjWaOZLIcoC3
(User Info Values)
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
(User Info Values)
Then, let's retrieve just the one team node and get some data
let teamsRef = self.ref.child("ftc-scouting-app").child("Teams")
let thisTeamRef = teamsRef.child("Jyis9009kos0kslk")
thisTeamRef.observeSingleEvent(of: .value, with: { snapshot in
let teamDict = snapshot.value as! [String: AnyObject]
let teamName = teamDict["Name"] as! String
print(teamName)
let memCount = teamDict["memberCount"] as! String
print(memCount)
let memberList = teamDict["memberList"] as! [String: AnyObject]
for user in memberList {
print(user.key)
}
})
and the output is
Brophy Robotics
2
5ilQc8KlrERLAmtFXjWaOZLIcoC3
syV9SS6S9hY8PyKBOC0VQ3NNv0v2
each event events asynchronously. you should use completion block in your each event.
func getNumber (completion: #escaping (String)->()){self.ref?.child("Teams").child(currentTeamID).child("Number").observeSingleEvent(of: .value, with: { (snapshot) in
let number1 = snapshot.value as? Int
if let teamNum = number1 {
completion(String(teamNum))
}
})}
getNumber(completion: {(teamNum) in
self.ref?.child("Teams").child(currentTeamID).child("memberCount").observeSingleEvent(of: .value, with: { (snapshot) in
let memcon = snapshot.value as? Int
if let membercount = memcon {
Cell.teamNumber.text = "team " + teamNum
Cell.userCount.text = "Members: " + String(membercount)
//breakpoint
}
})
})
My data structure is something like the following:
restaurant_owners
|
|owner_id (a unique ID)
|
|restaurant_name
|email
restaurant_menus
|
|restaurant_name
|
|dish_type (drinks, appetizer, etc...)
|
|dish_id (a unique ID)
|
|name
|
|price
The idea of the app is basically to allow "restaurant_owners" to login and manage the menu of their respective restaurant. However I am having problems with the following code: (note that the fetchDish function is called in viewDidLoad)
func fetchDish() {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
//first time referencing database
FIRDatabase.database().reference().child("owners").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
DispatchQueue.main.async{
restaurantName = dictionary["name"] as? String
print(restaurantName!)
}
}
})
//second time referencing database
FIRDatabase.database().reference().child("restaurants").child(restaurantName!).child("appetizer").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}, withCancel: nil)
}
What I am trying to do is to retrieve the the name of the restaurant for the current logged in user and store it in the variable "restaurantName". Then when I am referencing the database for the second time I can use this variable inside of .child (e.g.: .child(restaurantName)).
However, when I run this, I get an error saying that the restaurantName (in the database reference) is of value nil. I tried putting in some breakpoints and it seems like the first line of the second database reference is operated before whatever is "within" the first database reference, so basically restaurantName is called before any value is stored in it.
Why is this occurring? How do I work around this problem? Also, what are the best practices to achieve this if I'm doing it completely wrong?
NoSQL is very new to me and I have completely no idea how I should design my data structure. Thanks for the help in advance and please let me know if you need any other information.
UPDATE:
The problem was solved by changing my data structure to what Jay has suggested. The following code is what worked for me: (modified Jay's code a bit)
func fetchOwner() {
let uid = FIRAuth.auth()?.currentUser?.uid
let ownersRef = FIRDatabase.database().reference().child("owners")
ownersRef.child(uid!).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantID = dict["restaurantID"] as! String
self.fetchRestaurant(restaurantID: restaurantID)
}
}, withCancel: nil)
}
func fetchRestaurant(restaurantID: String) {
let restaurantsRef = FIRDatabase.database().reference().child("restaurants")
restaurantsRef.child(restaurantID).child("menu").observe(.childAdded, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
A couple of things:
Firebase is Asynchronous and you have to account for that in your code. As it is in the post, the second Firebase function may execute before the first Firebase function has successfully returned data i.e. restaurantName may be nil when the second call happens.
You should nest your calls (in this use case) to ensure data is valid before working with it. Like this.. and keep reading
let ownersRef = rootRef.child("owners")
let restaurantRef = rootRef.child("restaurants")
func viewDidLoad() {
fetchOwner("owner uid")
}
func fetchOwner(ownerUid: String) {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
ownserRef.child(ownerUid).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
restaurantId = dict["restaurant_id"] as? String
fetchRestaurant(restaurantId)
}
}
})
}
func fetchRestaurant(restaurantId: String) {
restaurantRef.child(restaurantId).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantName = dict["name"] as! String
let menuDict = dict["menu"] as! [String:Any]
self.dataSourceArray.append(menuDict)
menuTableView.reloadData()
}
}
}
Most importantly, it's almost always best practice to disassociate your key names from the data it contains. In this case, you're using the restaurant name as the key. What if the restaurant name changes or is updated? You can't change a key! The only option is to delete it and re-write it.... and... every node in the database that refers to it.
A better options it to leverage childByAutoId and let Firebase name the nodes for you and keep a child that has the relevant data.
restaurants
-Yii9sjs9s9k9ksd
name: "Bobs Big Burger Barn"
owner: -Y88jsjjdooijisad
menu:
-y8u8jh8jajsd
name: "Belly Buster Burger"
type: "burger"
price: "$1M"
-j8u89joskoko
name: "Black and Blue Burger"
type: "burger"
price: "$9.95"
As you can see, I leveraged childByAutoId to create the key for this restaurant, as well as the items on the menu. I also referenced the owner's uid in the owner node.
In this case, If the Belly Buster Burger changes to the Waist Slimming Burger, we can make one change and it's done and anything that references it is also updated. Same thing with the owner, if the owner changes, then just change the owner uid.
If the restaurant name changes to Tony's Taco Tavern, just change the child node and it's done.
Hope that helps!
edit: Answer to a comment:
To get the string (i.e. the 'key' of a key:value pair) immediately created by .childByAutoId()
let testRef = ref.child("test").childByAutoId()
let key = testRef.key
print(key)
I'm building an app using Firebase that displays a list of Questions in a table view. Each table view cell holds the text of the question, the name of the user that posted it, and the profile photo of the user that posted it.
Here is how I've structured my JSON tree:
"questions"
"firebase_autoID_01"
"name": "Jim"
"text": "Why is the earth round?"
"userID": "123456"
"userInfo"
"userID": "123456"
"photoURL": "gs://default_photo"
I'm trying to retrieve the name and text for each question and the photoURL of the user with the corresponding userID to then place those 3 elements in each table view cell. So I'm trying to retrieve data from the questions child node AND the separate userInfo child node to put that data into the same table view cell. Here is the code I've been trying to do that with:
When a user first gets created, I set their photoURL to a default image that I manually uploaded to Firebase Storage:
...
let placeholderPhotoRef = storageRef.child("Profile_avatar_placeholder_large.png")
let placeholderPhotoRefString = "gs://babble-8b668.appspot.com/" + placeholderPhotoRef.fullPath
//let placeholderPhotoRefURL = NSURL(string: placeholderPhotoRefString)
let data = [Constants.UserInfoFields.photoUrl: placeholderPhotoRefString]
self.createUserInfo(data)
}
func createUserInfo(data: [String: String]) {
configureDatabase()
let userInfoData = data
if let currentUserUID = FIRAuth.auth()?.currentUser?.uid{
self.ref.child("userInfo").child(currentUserUID).setValue(userInfoData)
}
}
Issues start occurring when I try to retrieve data from both the questions and userInfo:
var photoUrlArray = [String]()
func configureDatabase() {
ref = FIRDatabase.database().reference()
_refHandle = self.ref.child("questions").observeEventType(.ChildAdded, withBlock: {(snapshot) -> Void in
self.questionsArray.append(snapshot)
//unpack the userID from the "questions" child node to indicate which user to get the photoURL from in the "userInfo" child node
if let uid = snapshot.value?[Constants.QuestionFields.userUID] as? String {
self._photoURLrefHandle = self.ref.child("userInfo").child(uid).observeEventType(.ChildAdded, withBlock: {(snapshot) -> Void in
if let photoURL = snapshot.value as? String {
self.photoUrlArray.append(photoURL)
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.questionsArray.count-1, inSection: 0)], withRowAnimation: .Automatic)
}
})
}
})
}
I get the following error in the Xcode console:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'"
What I'm doing here is creating a firebase handle to observe/retrieve the users photoURL from the userInfo child, and that handle is created inside the closure of the handle that observes/retrieves the data from the questions child.
Questions: If this is not the right way to retrieve the data I want, how should I approach this? And how should I structure my JSON data if it isn't currently structure the right way?
Here is how I'm unpacking the name and text from the questions child node in the table view and it's working fine:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell! = self.tableView.dequeueReusableCellWithIdentifier("tableViewCell", forIndexPath: indexPath)
//unpack question from database
let questionSnapshot: FIRDataSnapshot! = self.questionsArray[indexPath.row]
var question = questionSnapshot.value as! Dictionary<String, String>
let name = question[Constants.QuestionFields.name] as String!
let text = question[Constants.QuestionFields.text] as String!
cell!.textLabel?.text = name + ": " + text
return cell!
}
Question: should I unpack the photoURL here or in the previous configureDatabase() function?
Since you asked for suggestion's on how to store your database structure in JSON. :-
Suggested JSON Structure : -
{"Users" : {
"userID_1" : {
userName : "Jim",
userImageURL : "gs://default_photo",
questionsAsked : {
"questionID_1" : "QUESTION TEXT",
"questionID_2" : "QUESTION TEXT"
}
}
"userID_2" : {
userName : "Moriarty",
userImageURL : "gs://default_photo",
questionsAsked : {
"questionID_1" : "QUESTION TEXT",
"questionID_2" : "QUESTION TEXT"
}
}
}
}
Since you are aiming to display the image and name of the user who posted the question with the question itself , i think it would be best if you created the child of questionAsked in the usersId itself that way you can keep track of the no and what questions were asked by that particular user.
Saving the user's profile data would go like this : -
FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser()!.uid).setValue([
userName : "Jim",
userImageUrl : //URL that you want to save
])
//Instead of FIRAuth.auth()!.currentUser()!.uid you can use childByAutoId too but i wont recommend that.
When user makes a post call a function with this code : -
let rootRef = FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser()!.uid).child("questionsAsked")
rootRef.observeEventType(.Value, withBlock: {(snap) in
if snap.exists(){
if let questionsDictionary = snap.value as? NSMutableDictionary{
questionsDictionary.setObject("Why is the earth round?", forKey: //your questionID)
rootRef.setValue(questionsDictionary)
}
}else{
rootRef.setValue([
your_questionID : "Why is the earth round?"
])
}
})
How to retrieve a question :-
let parentRef = FIRDatabase.database().reference().child("Users")
parentRef.observeEventType(.Value, withBlock: {(userSnap) in
if userSnap.exists(){
for eachUser in userSnap.value as! [String:AnyObject]{
let userId = eachUser.key as! String
parentRef.child(userId).observeEventType(.Value, withBlock: {(userSnap) in
if let userInfo = userSnap.value as? [String:AnyObject]{
let userNameRecieved = userInfo["userName"] as! String
let userImageUrlRecieved = userInfo["userImageURL"] as! String
parentRef.child(userId).child("questionsAsked").observeEventType(.Value, withBlock: {(usersQuestion) in
if userQuestion.exists(){
for eachQ in userQuestion.value as! [String:AnyObject]{
let question = eachQ.value as! String
//Create a function named - "initialiseCell" , that will initialise the datasource of the tableView with username, photoURL and question as its : String parameters
initialiseCell(question,userNameRecieved,userImageUrlRecieved)
}
}
})
}
})
}
}
})
As for yourQuestion_ID : -
let yourQuestionID = "\(Int(NSDate.timeIntervalSinceReferenceDate() * 1000))
This will produce an unique yourQuestionID every next second or maybe millisecond
This retrieve function in this stage, calls for every question in your entire apps database but you can filter it by modifying the code further...
This is how I ended up structuring my Firebase JSON tree:
"users":
"userID4321":
"displayName": "bob"
"photoURL": "gs://default_photo"
"questions":
"questionID5678":
"text": "How are you?"
"userID": "userID4321"
I then implemented the following code in my configureDatabase method:
func configureDatabase() {
ref = FIRDatabase.database().reference()
_refHandle = self.ref.child("questions").observeEventType(.ChildAdded, withBlock: {[weak self] (questionSnapshot) in
let questionID = questionSnapshot.key
var question = questionSnapshot.value as! [String: AnyObject]
question[Constants.QuestionFields.questionID] = questionID
let userID = question[Constants.QuestionFields.userID] as! String
let usersRef = self?.ref.child("users")
usersRef?.child(userID).observeEventType(.Value, withBlock: { (userSnapshot) in
var user = userSnapshot.value as! [String: AnyObject]
let photoURL = user[Constants.UserFields.photoUrl] as! String
let displayName = user[Constants.UserFields.displayName] as! String
question[Constants.QuestionFields.photoUrl] = photoURL
question[Constants.QuestionFields.displayName] = displayName
...
The observeEventType handle on the questions child gives me a snapshot of each question object every time a new child gets added or a new question gets created.
I then create a local dictionary from that question snapshot that was retrieved from the Firebase server.
With the userID from that question, I can then identify the corresponding user with another observeEventType method and retrieve that user's photoURL and append it to the question snapshot.
Once all the data I need is in the local question dictionary, I append it to my questionsArray which gets sent to the table view to display each question with the appropriate data.
Let me know if anyone who reads this would have done it differently!