iOSMoonSand iOSMoonSand - 3 months ago 50
Swift Question

Firebase - iOS Swift: load table view cell with data retrieved from two separate child nodes

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?

Answer

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!

Comments