Search code examples
swiftfirebasefirebase-realtime-databasefirebase-queue

Firebase Swift query and client side fan out


I have been on this issue for over three days, i have research and came across other similar questions on SO which relates to my issue but those fix could not solve mine hence the reason am asking this question.

I have a users, posts and users-posts node in firebase as shown below. I want to run a query on the node such that if two users are friends they can see each others post. But if they are not friends they cannot see each others posts

Users
 123840ajldkjfas0d9
   username: Joe
   friend
     78983049802930laks: true 
     78983049802930laks: true

 4563049802930laks
   username: Ken
   friend
     123840ajldkjfas0d9: true

 78983049802930laks
   username: Pean
   friend
      123840ajldkjfas0d9: true 

posts
 876f92fh02hfj02930239
   post: This is cool
   whoposted: 123840ajldkjfas0d9

 39fh938hqw9320923308
   post: I love pizza
   whoposted: 78983049802930laks

 users-posts
  123840ajldkjfas0d9
   876f92fh02hfj02930239: true

  78983049802930laks
   39fh938hqw9320923308: true

This is my query currently, it is showing all post for all users whether they are friends or not. Please i need help with this.

    DataService.ds.REF_USERS.observe(.value, with: { (userSnapshot) in
    if let snapshot = userSnapshot.children.allObjects as?   
    [FIRDataSnapshot]{
            for userSnap in snapshot{
                print("snapshot.key: \(userSnap.key)")
                let userKey = userSnap.key
                if var userDict = userSnap.value as? Dictionary<String, 
    AnyObject>{

    let postUserPicUrl = userDict["profileImgUrl"] as? String

    if let firstName = userDict["firstName"] as? String{
                        ("firstName: \(firstName)")

    DataService.ds.REF_POST.observeSingleEvent(of: .value, with: { 
    (postSnapshot) in
    if let postSnapshot = postSnapshot.children.allObjects as? 
    [FIRDataSnapshot]{
    for postSnap in postSnapshot{
    if var postDict = postSnap.value as? Dictionary<String, AnyObject>{
    if let refPostUserKey = postDict["user"] as? String{
    if userKey == refPostUserKey{ 

    DataService.ds.REF_BLOCK_USER.observeSingleEvent(of: .value, with: {
    (blockUserSnapshot) in
    if let blockUserSnapshot = blockUserSnapshot.children.allObjects as?
    [FIRDataSnapshot] {
    for blockUserSnap in blockUserSnapshot{
    if var blockUserDict = blockUserSnap.value as? Dictionary<String, 
    AnyObject> {
    if let      user = blockUserDict["user"] as? String{                          
    if firstName != user {                                                                                                                                                               
    postDict["postUserPicUrl"] = postUserPicUrl  as AnyObject?;                                                    
        let postKey = postSnap.key
        let post = Post(postKey: postKey, postData: postDict)

        self.posts.append(post)

                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                             self.tableView.reloadData()
                                                })




                                            }
                                        }
                                    }
                                }
                            }
                          self.tableView.reloadData()
                        })
                    }
                 }
              }
          }
         self.tableView.reloadData()
      })
   }

Solution

  • I mean this with no disrespect, but you are not utilizing these queries well with each nested within another. Also, make sure you update all of your queries. The Post query uses the old formatting while your user query is up to date.

    You should create 3 dictionaries to hold the data for each node Users, posts, users-posts as well as a var to hold the current user string and a dictionary to contain the post data:

    var users = [String:Any]()
    var posts = [String:Any]()
    var usersposts = [String:Any]()
    var currentUserKey:String!
    var visibleposts = [String:Any]()
    

    Then have three separate queries to get the data. Currently it does not appear that you are querying for any specific users so I will do the same:

    func getUserData(){
        DataService.ds.REF_USERS.observe(.childAdded, with: {snapshot in 
            let key = snapshot.key
            let data = snapshot.value as? [String:Any] ?? [:]
            self.users[key] = data
        })
    }
    func getPostsData(){
        DataService.ds.REF_POST.observe(.childAdded, with: {snapshot in 
            let key = snapshot.key
            let data = snapshot.value as? [String:Any] ?? [:]
            self.posts[key] = data
            self.refreshPosts()
        })
    }
    func getUsersPostsData(){
        DataService.ds.REF_BLOCK_USERS.observe(.childAdded, with:{snapshot in // I am guessing you have the users posts here?? there doesn't seem to be sample data for blocked users in your OP
            let key = snapshot.key
            let data = snapshot.value as? [String:Any] ?? [:]
            self.usersposts[key] = data
            self.refreshPosts()
        })
    }
    

    Now get the current user before firing off these queries in the view did load and then call each query.

    override func viewDidLoad(){
        self.currentUserKey = (FIRAuth.auth()?.currentUser?.uid)! 
        /* you may want to do some error handling here to ensure the user 
        is actually signed in, for now this will get the key if 
        they are signed in */
        self.getUserData()
        self.getPostsData()
        self.getUsersPostsData()
    
        // data will be refreshed anytime a child is added
    }
    func refreshPosts(){
        self.validposts = [:]
        let validUsers = [String]() // this will hold the valid keys to get posts
        validUsers.append(self.currentUserKey)
        let currentUserData = users[self.currentUserKey] // filter the current user data to get the friends
        // get friends keys
        let friendsData = currentUserData["friends"] as? [String:Any] ?? [:]
        for key in friendsData.keys {
            // add friends posts to the validposts data
            validUsers.append(key)
        }
        // get current users posts:
        for (key,value) in self.posts {
            let postData = value as? [String:Any] ?? [:]
            let whoposted = postData["whoposted"] as? String ?? ""
            if validUsers.contains(whoposted){
                self.validposts[key] = postData
            }
        }
        // access the self.validposts data in your UI however you have it setup
    
        // The child added queries above will continue to fire off and refresh 
        // your data when new posts are added.
    
        // I am still not clear what the usersposts data is for so it is omitted here.
    }