KingTim
KingTim

Reputation: 1301

Firebase observer reading data twice

My observer listens for new rooms being added in Firebase then updates a local array when one is added. It's working except when I add a room to Firebase, two of that room are added to the array. For example if I have 4 rooms in Firebase (Room One, Room Two, etc), when the app loads, there are 8 rooms in the table view (two Room One's, two Room Two's, etc). Then adding, for example, Room Five, two Room Fives would show up.

private func observeRooms() {

    guard let uid = FIRAuth.auth()?.currentUser?.uid else {print("Error getting user UID"); return}
    let userRoomRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").child(uid).child("rooms")

    userRoomRef.observe(.childAdded, with: { (roomSnap) in
        for eachRoom in roomSnap.value as! [String : AnyObject] {
            let roomID = eachRoom.key as! String
            print("Roomsnap 2 (observer): \(roomSnap.value)")

            if let roomInfo = roomSnap.value as? [String : AnyObject] {

                guard let roomName = roomInfo["roomName"] as! String! else {print("Error getting room name"); return}
                guard let participants = roomInfo["participants"] as! [String]! else {print("Error getting room participants"); return}
                print("\n\nRoom Name: \(roomName)\nRoom Participants: \(participants)\nRoom ID: \(roomID)\n")

                self.usersRooms.append(Room(id: roomID, name: roomName, participants: participants))

                print("User's Rooms: \(self.usersRooms)\n")
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }

        }
    })
}

Why is the data being fetched twice? Is there a way to write the function to just read each room once?

Firebase DB JSON:

"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
  "gender" : "male",
  "handle" : "TestHandleOne",
  "name" : "Timothy",
  "profilePicture" : "https://graph.facebook.com/*removed*/picture?type=large&return_ssl_resources=1",
  "rooms" : {
    "-KhY2GnJOxBwdPK669ui" : {
      "participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
      "roomName" : "Room One"
    },
    "-KhY2Hnz48lTtRpzmBuw" : {
      "participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
      "roomName" : "Room Two"
    },
    "-KhY2IZL4l16dMxGopt6" : {
      "participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
      "roomName" : "Room Three"
    },
    "-KhY8SdHnkfI7bZIpyjI" : {
      "participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
      "roomName" : "Room Four"
    }
  }
}

roomSnap.value printed to the console, each room printed twice with different values for Room ID. This is Room One for example:

enter image description here

Working function (final edit):

private func observeRooms() {

    guard let uid = FIRAuth.auth()?.currentUser?.uid else {print("Error getting user UID"); return}
    let userRoomRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").child(uid).child("rooms")


    roomRefHandle = userRoomRef.observe(.childAdded, with: { (snapshot) -> Void in

        let roomData = snapshot.value as! Dictionary<String, AnyObject>
        let id = snapshot.key
        guard let name = roomData["roomName"] as! String! else {print("Error getting user name"); return}

        self.usersRooms.append(Room(id: id, name: name, participants: [uid]))

        // Add new room to "rooms" in Firebase
        let roomDict = ["roomName" : name, "participants": [uid]] as [String : Any]
        let newRoomRef = self.roomRef.child(id)
        newRoomRef.setValue(roomDict)

        self.tableView.reloadData()
    })
}

Upvotes: 0

Views: 1874

Answers (1)

Jay
Jay

Reputation: 35677

The .childAdded function reads in each child, one at a time, when first called and then any new children thereafter.

This event is triggered once for each existing child and then again every time a new child is added to the specified path. The listener is passed a snapshot containing the new child's data.

The key here is that it automatically iterates over each room in the node one at a time. The code in the question is redundant which leads to multiple entries in the array.

Here's a super short way to populate the array with each child node

    let usersRef = self.ref.child("users")

    let uid = "user_0" //some uid
    let userRoomRef = usersRef.child(uid).child("rooms")

    userRoomRef.observe(.childAdded, with: { roomSnap in

        let roomDict = roomSnap.value as! [String: AnyObject]

        let roomID = roomSnap.key
        let roomName = roomDict["roomName"] as! String
        print("roomID = \(roomID)  roomName = \(roomName)")

        var aRoom = Room() //could also initWith...
        aRoom.id = key
        aRoom.name = roomName
        //aRoom.participants = partipants
        self.usersRooms.append(aRoom)
        self.tableView.reloadData()
    })

Edit

In response to a comment, an alternative structure is

users
  uid_0
   name: "Tom"
  uid_1
   name: "Jerry"

rooms
  room_0
   name: "Cool Room"
    users:
     uid_0: true
     uid_1: true
  room_1
   name: "Romper Room"
    users
     uid_0: true

Tom and Jerry both belong to the Cool Room, but only Tom belongs to Romper Room.

If you want to populate an array/tableView with the rooms that Tom belongs do, you can simply deep query on the rooms/users/ where uid_0 == true.

Upvotes: 1

Related Questions