0

I am new to Swift 3 and Firebase so am having some issues with retrieving data from two different nodes and displaying them in one table view cell. I have found some other similar questions on Stack Overflow like here:

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

but could not apply it to my code as either these examples were too specific to the person asking or my lack of knowledge around Firebase has prevented me from progressing with the answers supplied.

My application is written in Swift 3 using Xcode-8 and uses Firebase for data persistence. The aim of the application is to allow users to submit different exercise programs for consumption by other users. User submitted programs have the author's uid associated with them, I was planning to use this to then retrieve the user's username from a separate node based off this uid value.

My Firebase set up:

    "programs" : {
    "-KYF3o3YD6F3FEXutuYH" : {
      "content" : "Program Content Goes Here...",
      "duration" : "4 Weeks",
      "title" : "Chest Blast",
      "type" : "Hypertrophy",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    },
    "-KYF4ev88FQ2nEr6yTOW" : {
      "content" : "Program Content Goes Here...",
      "duration" : "6 Weeks",
      "title" : "Full Back Workout",
      "type" : "Strength",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    },
    "-KZRYF9A8-8OHCNzOoPT" : {
      "content" : "Eugene and Eamon",
      "duration" : "4 Weeks",
      "title" : "abc",
      "type" : "abc",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    },
    "-KbKNdrAcBarpaNoGf_e" : {
      "content" : "Test",
      "duration" : "test",
      "title" : "New Test",
      "type" : "test",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    },
    "-KbKnXnyzj_EJp_wNw5y" : {
      "content" : "1. Barbell Bench Press\n\nWhy it's on the list: You can generate the most power with barbell lifts, so the standard barbell bench allows you to move the most weight. It's also an easier lift to control than pressing with heavy dumbbells. The exercise is easy to spot and relatively easy to learn (if not master), There are plenty of bench-press programs you can follow to increase your strength.\n\n1. Barbell Bench Press\n\nWhy it's on the list: You can generate the most power with barbell lifts, so the standard barbell bench allows you to move the most weight. It's also an easier lift to control than pressing with heavy dumbbells. The exercise is easy to spot and relatively easy to learn (if not master), There are plenty of bench-press programs you can follow to increase your strength.",
      "duration" : "1",
      "title" : "1",
      "type" : "1",
      "uid" : "oLy9GOzDyKht7WWVZgpd3jPHxsE3"
    }
  },
  "users" : {
    "hds1WketiAUELVDOz1Dprvlu0KE3" : {
      "username" : "Example"
    },
    "oLy9GOzDyKht7WWVZgpd3jPHxsE3" : {
      "username" : "Test"
    }
  }

The table in the application looks like this:

Table View

As you can see, at the moment the author is just indicated by their uid. This was easy as I have it stored in the same place as all the other data being displayed but I need to use that uid value to search another node and grab the username associated with it and display it where the uid is shown now.

Below is the code I am using to retrieve and display the data. My main questions are:

1: What query can I use to search the users nodes for the matching uid and grab just the username of that person and display it in the table view?

2:Where should I place that query in the code? I was thinking to just place it in the func tableView() method as I could just search the user each time a new post is being added to the cell and that way I wouldn't have to make another NSMutable array to hold users who may not even have posts. Many thanks in advance for any help offered.

@IBOutlet weak var programsTableView: UITableView!
var programs = NSMutableArray()

override func viewDidLoad() {
    super.viewDidLoad()

    self.programsTableView.delegate = self
    self.programsTableView.dataSource = self

    //Call load data method to populate table with data from Firebase
    loadData()
}

//Method to load data from Firebase
func loadData(){

FIRDatabase.database().reference().child("programs").observeSingleEvent(of: .value, with: { (snapshot) in
        //Snapshot holds value and it is casted to NS Dictionary
        if let programsDictionary = snapshot.value as? [String: AnyObject]{
            for program in programsDictionary{
                self.programs.add(program.value)
            }
            self.programsTableView.reloadData()
        }
    })
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! AllProgramsTableViewCell

    // Configure the cell...
    let program = self.programs[indexPath.row] as! [String: AnyObject]

    //Populate row
    //Grab title and add it to cell
    cell.titleLabel.text = program["title"] as? String
    //Grab type and add it to cell
    cell.typeLabel.text = program["type"] as? String
    //Grab duration and add it to cell
    cell.durationLabel.text = program["duration"] as? String
    //Grab content and add it to cell
    cell.contentTextView.text = program["content"] as? String
    //Grab author and add it to cell
    cell.authorLabel.text = program["uid"] as? String

    return cell
}
Community
  • 1
  • 1
ACJ
  • 3
  • 3
  • 1
    Please remove the image of your Firebase structure and post the actual Firebase structure as text please, no images. That can be obtained from your Firebase Console->Three dots on right->Export JSON. It's important to use text as it is searchable, and helps us to help you as we don't have to retype it in an answer. – Jay Jan 28 '17 at 14:02
  • @Jay I made those changes. Apologies. – ACJ Jan 28 '17 at 21:07

2 Answers2

3

A few things to help you out.

1: What query can I use to search the users nodes for the matching uid and grab just the username of that person and display it in the table view?

First, change your users node and leverage the uid as the key for each user

users
  uid_0
    name: "Hans"
    userName: "pump_u_up"
  uid_1
    name: "Franz"
    userName: "abs_of_steel"

That avoids having to query. Queries are 'heavier' than observes and take more resources. By using the uid, you can directly capture the user info. For example:

let userRef = rootRef.child("uid from programs node")
userRef.observeSingleEvent(of: .value, with: { snapshot in
    let userDict = snapshot.value as! [String:AnyObject]
    let userName = userDict["userName"] as! String
    print(userName)
})

2:Where should I place that query in the code? I was thinking to just place it in the func tableView() method as I could just search the user each time a new post is being added to the cell and that way I wouldn't have to make another NSMutable array to hold users who may not even have posts. Many thanks in advance for any help offered.

Now that you have the code to capture the user info, adding it is a snap, however, I would suggest a slightly different approach than what's posted.

Start with a ProgramClass

class ProgramClass {
    var key = ""
    var content = ""
    var duration = ""
    var username = ""
}

and populate the datasource thusly:

ref.child("programs").observeSingleEvent(of: .value, with: { (snapshot) in
     for snap in snapshot.children {
          let programSnap = snap as! FIRDataSnapshot
          let programKey = programSnap.key //the key of each program
          let programDict = programSnap.value as! [String:AnyObject] //program child data
          var aProgram = ProgramClass()
          aProgram.key = programKey
          aProgram.content = programDict["content"] as! String
          //etc
          let uid = programDict["uid"] as! String
          let userRef = ref.child("users").child(uid)
          userRef.observeSingleEvent(of: .value, with: { snapshot in
             let userDict = snapshot.value as! [String:AnyObject]
             let userName = userDict["userName"] as! String
             aProgram.username = userName
             self.programsArray.append[aProgram]
        })
    }
}

The above code could be significantly shortened but leaving it more verbose for readability.

Oh, and don't forget to define your datasource array in a Swifty way:

var programsArray = [ProgramClass]()
Jay
  • 34,438
  • 18
  • 52
  • 81
  • i like the idea with the data class (also a lightweight struct is here possible) – muescha Jan 28 '17 at 16:29
  • @Jay Thank you for taking the time to post an answer Jay. Unfortunately I am new to both Swift and Firebase so your answer is slightly over my head. I have made the changes to the users node. The uid is now the key and its child is the username. For the sake of getting this working at a basic level would it be possible for you to provide and explain the query or "observes" for parsing the users nodes and just grabbing the username of the person matching the uid from programs and add it to the table view. While not the best practice even putting it in the tableView function would be great! – ACJ Jan 28 '17 at 21:38
  • Hi Jay, I modified your `let userRef = rootRef.child("uid from programs node") userRef.observeSingleEvent(of: .value, with: { snapshot in let userDict = snapshot.value as! [String:AnyObject] let userName = userDict["userName"] as! String print(userName) })` code and seem to have got the usernames displaying. I now have to sort out adding usernames to the database in this new way as I just used the console to make the changes but this is a great start, thank you. – ACJ Jan 28 '17 at 22:02
  • @ACJ Great! If it helped, please accept the answer. – Jay Jan 29 '17 at 00:24
-1

UPDATE: getUserName is not working that way because observeSingleEvent is a asyc call. (thanks to @Aodh)

PS: i don't know if it's better to delete the answer or leave it as a bad example?


the solution is just retrieve again the user data:

you should change your schema that you don't save the uid at programs but the userId

"programs":
     "programId1234":
         [...]
         "userId": "userid12345
"users":
     "userId1234":
         "uid": "uidxxxxxx"
         "username" : "jim"

code:

FIRDatabase.database().reference().child("programs").observeSingleEvent(of: .value, with: { (snapshot) in
        //Snapshot holds value and it is casted to NS Dictionary

        //change to var!
        if var programsDictionary = snapshot.value as? [String: AnyObject]{
            for program in programsDictionary{
                // add there
                let programDictionary = program.value as? [String: AnyObject]
                let authorUid = programDictionary["userId"] as? String
                let authorName = getUserName(forUid: authorUid)
                programDictionary["authorname"] = autorName
                //
                self.programs.add(programDictionary)
            }
            self.programsTableView.reloadData()
        }
    })


func getUserName(forUid userID: String) -> String {
    var userName = nil
    FIRDatabase.database().reference().child("users").child(userID)
         .observeSingleEvent(of: .value, with: { (snapshot) in
              if let userDictionary = snapshot.value as? [String: AnyObject]{
                  userName = userDictionary["username"] as? String
              }
        })
    return userName

}

and later dont need to convert it anymore - you can change this line:

// Configure the cell...
let program = self.programs[indexPath.row] as! [String: AnyObject]

to

// Configure the cell...
let program = self.programs[indexPath.row]

here is an example for join calls: https://firebase.googleblog.com/2013/10/queries-part-1-common-sql-queries.html#join

muescha
  • 1,544
  • 2
  • 12
  • 22
  • I like the answer but there are a lot of coding issues. Can you please fix and update? i.e. let authorUid = program["userId"] as? String won't work as program doesn't have subscript members and var userName = nil is not a valid statement (Swift3) – Jay Jan 28 '17 at 14:58
  • also, why is just the value being added to the array with self.programs.add(program.value) – Jay Jan 28 '17 at 15:03
  • program.key was the key of this node - and program.value all the child nodes. in the question you see that is always convertet in cellForRow – muescha Jan 28 '17 at 16:28
  • @muescha Thank you for taking the time to post your answer, I managed making some progress with Jay's answer so I think I can move on with my app. Thank you again. – ACJ Jan 29 '17 at 10:22
  • @muescha How can getUsername work?? It's an asynchronous call right? – Aodh Jan 30 '18 at 15:11
  • 1
    @Aodh - thats right there is a bug - it is not working that way. i will update my answer – muescha Jan 31 '18 at 16:08