Newbie
Newbie

Reputation: 358

How to fetch nested data from Firebase realtime database by implementing observer in swift

Firebase data-structure

  1. lastLocations ( batteryStatus, lat, long, timestamp, uid)
  2. profiles (name, phoneNumber, picture, uid)
  3. userFriends ( basis on the uid -> how many friends -> conversationUid, friendStatus, notify, phoneNumber, uid)

My Code:

Issues:

Results to achieve:

Firebase JSON

{
  "lastLocations": {
    "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
      "batteryStatus": 22,
      "latitude": 40.9910537,
      "longitude": 29.020425,
      "timeStamp": 1556568633477,
      "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
    },
    "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
      "batteryStatus": 88,
      "latitude": 41.0173995,
      "longitude": 29.1406086,
      "timeStamp": 1571778174360,
      "uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
    }
  },
  "profiles": {
    "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
      "fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
      "name": "Skander",
      "phoneNumber": "+95644125503",
      "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
    },
    "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
      "fcmToken": "enMneewiGgg:APA91bHyA4HypWUYhxGTUTTch8ZJ_6UUWhEIXRokmR-Y-MalwnrtV_zMsJ9p-sU_ZT4pVIvkmtJaCo7LFJYJ9ggfhc1f2HLcN9AoIevEBUqyoMN-HDzkweiUxAbyc84XSQPx7RZ1Xv",
      "name": "Murad",
      "phoneNumber": "+915377588674",
      "picture": "profile/zzV6DQSXUyUkPHgENDbZ9EjXVBj2/a995c7f3-720f-45bf-ac58-b2df934e3dff.jpeg",
      "uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
    }
  },
  "userFriends": {
    "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
      "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
        "conversationUid": "-L_w2yi8gh49GppDP3r5",
        "friendStatus": "STATUS_ACCEPTED",
        "notify": true,
        "phoneNumber": "+915377588674",
        "uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
      }
    },
    "zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
      "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
        "conversationUid": "-L_w2yi8gh49GppDP3r5",
        "friendStatus": "STATUS_ACCEPTED",
        "notify": true,
        "phoneNumber": "+915644125503",
        "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
      }
    }
  }

}

Swift Function:

func getFrndDataList(){
        AppData.removeAll()
        ref.child("userFriends").child("zzV6DQSXUyUkPHgENDbZ9EjXVBj2").observe(.childAdded, with: { (snapshot) in

           guard let data = try? JSONSerialization.data(withJSONObject: snapshot.value as Any) else { return }
           let frndList = try? JSONDecoder().decode(Friend.self, from: data)

           self.AppData.append(frndList!)
           self.tableView.reloadData()
           print([frndList])
        })
    }

Upvotes: 1

Views: 898

Answers (1)

Jay
Jay

Reputation: 35659

Note: After writing this answer I realized it was way long but this is a big question and there are a lot of elements to address.

My first suggestion is to change the structure as it's overly complicated for what's being done with the data. Also, there is repetitive data that's not needed so that should be changed as well. For example, here's your profiles node

  "profiles": {
    "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
      "fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
      "name": "Skander",
      "phoneNumber": "+95644125503",
      "uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1" <- remove this, not needed.
    },

As you can see, each child node has a key of the user id. But, you are also storing the user id as a child node as well. They key is the uid and will always be available so no need for duplication there and the child node should be removed.

Based on comments, this is a much better structure

/users
   FTgzbZ9uWBTkiZK9kqLZaAIhEDv1
      "batteryStatus": 22,
      "latitude": 40.9910537,
      "longitude": 29.020425,
      "timeStamp": 1556568633477,
      "fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
      "name": "Skander",
      "phoneNumber": "+95644125503",
      "conversationUid": "-L_w2yi8gh49GppDP3r5",
      "friendStatus": "STATUS_ACCEPTED",
      "notify": true,
      "phoneNumber": "+915377588674",

and then, to keep track of a users friends, it becomes this

/userFriends
   zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
      FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: true //their friend
      IRoo0lbhaihioSSuFETngEEFEeoi: true //another friend

To load this users friends, we read the data at /userFriends/this_users_id and then iterate over the child nodes loading the data for display in the tableView

Lets start with an object that will be used to hold each friends data, and then an array that will be used as a tableView Datasource

class FriendClass {
    var uid = ""
    var name = ""
    //var profilePic
    //var batteryStatus

    init(withSnapshot: DataSnapshot) {
        self.uid = withSnapshot.key
        self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
    }
}

var myFriendsDataSource = [FriendClass]()

Then a functions to read the users node, iterate over the users friends uid's and read in each users data, populating the FriendClass object and storing each in an array. Note that self.ref points to my firebase.

func loadUsersFriends() {
    let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
    let myFriendsRef = self.ref.child("userFriends").child(uid)
    myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
        let uidArray = snapshot.children.allObjects as! [DataSnapshot]
        for friendsUid in uidArray {
            self.loadFriend(withUid: friendsUid.key)
        }
    })
}

func loadFriend(withUid: String) {
    let thisUserRef = self.ref.child("users").child(withUid)
    thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
        let aFriend = FriendClass(withSnapshot: snapshot)
        self.myFriendsDataSource.append(aFriend)
    })
}

Now that we have the code to read in the data, you also want to watch for changes. There are a number of options but here's two.

1) I'll call this brute force.

Simply attach a .childChanged observer to the /users node and if something changes, that changed node is passed to the observer. If the key to that node matches a key in myFriendsDataSource array, update that user in the array. If no match, then ignore it.

func watchForChangesInMyFriends() {
    let usersRef = self.ref.child("users")
    usersRef.observe(.childChanged, with: { snapshot in
        let key = snapshot.key
        if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
            let friend = self.myFriendsDataSource[friendIndex]
            print("found user \(friend.name), updating")
            //friend(updateWithSnapshot: snapshot) //leave this for you to code
        }
    })
}

2) Selective observing

For this, we simply attach an .childChanged observer to each friend node - and that can be done within the code example from above

func loadFriend(withUid: String) {
    let thisUserRef = self.ref.child("users").child(withUid)
    thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
        let aFriend = FriendClass(withSnapshot: snapshot)
        self.myFriendsDataSource.append(aFriend)
        //add an observer to this friends node here.
    })
}

One last thing: I didn't address this

"friendStatus": "STATUS_ACCEPTED",

I would think that only friends you accepted are in the friends list so the use is a tad unclear. However, if you want to use it you could do this

/userFriends
   zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
      FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: "STATUS_ACCEPTED"
      IRoo0lbhaihioSSuFETngEEFEeoi: "STATUS_DECLINED"

and then as you're itering over friends to load, ignore the ones that are declined.

If you MUST keep your current structure (which I do NOT recommend) the techniques in this answer will work for that structure as well, however, it will be a lot more code and you're going to be moving around a lot of unneeded extra data so the Firebase bill will be higher.

Upvotes: 1

Related Questions