简体   繁体   中英

Query multiple keys on Firebase

I'm following Firebase's recommendation of flattening data, but I'm having trouble listing a series of items from my database.

Here's a sample of my database file:

"users" : {
    "UID12349USER" : {
      "firstName" : "Jon",
      "lastName" : "Snow",
      "email" : "jonsnow@winterfell.com",
      "albums" : {
        "UID124ALBUM" : true,
        "UID125ALBUM" : true
      }
    }
},
"albums" : {
    "UID124ALBUM" : {
      "name" : "My Artwork",
    },
    "UID125ALBUM" : {
      "name" : "My Sketches",
    }
}

I'm retrieving the list of albums for a given user:

let userAlbums = database.child(usersKey).child(user.uid).child(albumsKey)
userAlbums.observeSingleEventOfType(.Value, withBlock: { snapshot in
    // fetch [UID124ALBUM: 1, UID125ALBUM: 1]
})

Now I wish I could retrieve all the user's albums in one single query. I could do a batch of queries, and populate an asynchronous array, but that doesn't seem like a good approach to me...

for key in albumKeys {
    let album = database.child(self.albumsKey).child(key)
    album.observeSingleEventOfType(.Value, withBlock: { snapshot in
        // fetch album.. append to array
    })
}

Using that approach makes it tricky to detect when the queries have finished, due to the asynchronous nature of the requests. Add to that the fact that some of the requests might fail, due to a bad connection.

Also, if I want to filter one of the albums with a given name (eg "My Artwork") or return nil if it doesn't exist, I also end up with a tricky end condition.

var found = false

for key in albumKeys {
    let album = database.child(self.albumsKey).child(key)
    album.observeSingleEventOfType(.Value, withBlock: { snapshot in
        // if current.name == "My Artwork"
        // completion(current)
    })
}
// This block will be called before observeSingleEventOfType =.=
if !found {
    completion(nil)
}

I have a good background on iOS and Swift, but I'm knew to Firebase and NoSQL databases. Can someone point me a good direction? Should I ditch Firebase and try something else? Am I missing some method that can query what I need? Is my json structure wrong and missing some extra keys?

Thanks

I would suggest using a DispatchGroup and mutual exclusion to handle asynchronous functions within a for loop. Here is the code you provided with a DispatchGroup to ensure that all of the asynchronous functions in the loop have completed before it checks the if statement:

let myGroup = DispatchGroup()
var found = false
// iterate through your array
for key in albumKeys {
    let album = database.child(self.albumsKey).child(key)
    // lock the group 
    myGroup.enter()
    album.observeSingleEventOfType(.Value, withBlock: { snapshot in
        if current.name == "My Artwork" {
             found = true
        }
        // after the async work has been completed, unlock the group
        myGroup.leave()
    })
}
// This block will be called after the final myGroup.leave() of the looped async functions complete
myGroup.notify(queue: .main) {
    if !found {
        completion(nil)
    }
}

Anything contained in the myGroup.notify(queue: .main) { codeblock will not execute until myGroup.enter() and myGroup.leave() have been called the same amount of times. Be sure to call myGroup.leave() within the Firebase observe block (after the async work) and make sure that it is called even if an error is produced from the observe.

In your case, the only possible way around is to call another listener from inside of a listener. That way, you wouldn't need to separately handle the asynchronous nature of requests.

For example, in your case:-

let userAlbums = database.child(usersKey).child(user.uid).child(albumsKey)

userAlbums.observeSingleEventOfType(.Value, withBlock: { snapshot in
 if(snapshot.exists()) {
      // fetch [UID124ALBUM: 1, UID125ALBUM: 1] in the albumKeys
        for key in albumKeys {
          let album = database.child(self.albumsKey).child(key)
          album.observeSingleEventOfType(.Value, withBlock: { snapshot in 
                   // fetch album.. append to array
         })
        }
 }
})

Now, to filter one of your albums, you may use completion like this inside a function:

 var found = false
 let userAlbums = database.child(usersKey).child(user.uid).child(albumsKey)

 userAlbums.observeSingleEventOfType(.Value, withBlock: { snapshot in
     if(snapshot.exists()) {
        // fetch [UID124ALBUM: 1, UID125ALBUM: 1] in the albumKeys
        for key in albumKeys {
          let album = database.child(self.albumsKey).child(key)
          album.observeSingleEventOfType(.Value, withBlock: { snapshot in 
                   // if current.name == "My Artwork"
                   found = true
                   completion(current)
         })
        }
  }
 })
 if !found {
    completion(nil)
 }

I'm new to both iOS and firebase, so take my solution with a grain of salt. Its a workaround, and it might not be airtight.

If I understood your question correctly, I'm facing a similar issue. You have "users" and "albums". I have "users" and "globalWalls". Inside "users" I have "wallsBuiltByUser" which holds keys of globalWalls.

I wanted to loop through wallsBuiltByUser and retrieve their corresponding node from globalWalls. At the end, I need to call my delegate notifying that the walls have been retrieved.

I believe this might be similar to what you are trying to do.

Here is my solution:

       databaseRef.child("users/\(userID)/WallsBuiltByUser/").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot:FIRDataSnapshot) in


        let numOfWalls = Int(snapshot.childrenCount)
        var wallNum:Int = 0

        for child in snapshot.children {

            let wallId = (child as! FIRDataSnapshot).key

            self.databaseRef.child("globalWalls").child(wallId).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in

                wallNum = wallNum + 1
                let wallServerInfo = snapshot.value as? NSDictionary!

                if (wallServerInfo != nil){
                    let wall = Wall(wallInfo: wallServerInfo)
                    self.returnedWalls.append(wall)
                }

                if(wallNum == numOfWalls){
                    print("this should be printed last")
                    self.delegate?.retrieved(walls: self.returnedWalls)
                }
            })//end of query of globalWalls


        }//end of for loop


    })//end of query for user's walls

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM