containedIn query for Firebase - ios

Coming from Parse, I have heavily relied on the containedIn query to collect the right data. In Parse, I might have had an array of objectIds and queried for all objects with those ids. I am looking to achieve the same on Firebase.
I understand that it is important to flatten data, but I don't see how this helps with the problem. Let's say I have a chat room with a list of users inside it. I collect that data and now have an array of usernames. I would like to now navigate to the users in the database and retrieve all that match one element inside this username array. How can I accomplish something like this?
For example, a set of users in a official Firebase example:
{
"users": {
"alovelace": { ... },
"ghopper": { ... },
"eclarke": { ... }
}
}
I would like to perform a query to download the following users:
["alovelace", "eclarke"]
While a general answer would be helpful, an answer in Swift would be best. Thank you.

An example is that they are the two members of a chat room. Or that
the current user is following them.
So a theoretical users node
users
alovelace
followed_by
bill: true
frank: true
in_chat_room: room_42
location: France
ghopper
followed_by
jay: true
in_chat_room: room_27
location: USA
eclarke
followed_by
frank: true
in_chat_room: room_42
location: Canada
and some chat rooms
chat_rooms
room_27
ghopper: true
room_42
lovelace: true
eclarke: true
To get the detailed users nodes of users in chat room 42 (lovelace, eclarke)
let usersRef = self.myRootRef.childByAppendingPath("users")
usersRef.queryOrderedByChild("in_chat_room").queryEqualToValue("room_42")
.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let location = child.value["location"] as! String
print(location) //prints France and Canada
}
})
To get the users Frank is following (lovelace, eclarke):
let usersRef = self.myRootRef.childByAppendingPath("users")
usersRef.queryOrderedByChild("followed_by/frank").queryEqualToValue(true)
.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let userName = child.key as String
print(userName)
}
})
Note that using user names as node key's is generally a bad idea - they should be stored by their uid.
Also note we didn't really do anything with the chat_rooms node but to maintain the relationship, having nodes refer to each other can be useful for observing changes etc.
Edit:
In response to a comment, here's the structure for each user to show who they are following instead of who is following the user
users
alovelace
following_user
bill: true
frank: true
in_chat_room: room_42
location: France
with this structure, alovelace is following bill and frank.
To get all of the users following frank:
let usersRef = self.myRootRef.childByAppendingPath("users")
usersRef.queryOrderedByChild("following_user/frank").queryEqualToValue(true)
.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let userName = child.key as String
print(userName)
}
})

Related

How to queryOrderedbyChild and queryEqualtoValue with nested data in Firebase?

Histories
Places
ChIJ-REgsXyPwTARJh1vn16yZBc (placeID)
name: "Tokyo Tower"
userInfo
M1nlb7iEg9ZU6cr28DOwJOqrlKA2 (userID)
Lxqcl6Zys2cjmYSzcWU (autoID)
dateAdded: "2020-01-05"
interactionType: "placeTap"
let historiesPlacesRef = Database.database().reference().child("Histories").child("Places")
When I fetch the place with the name "Tokyo Tower" using the following, it works.
historiesPlacesRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: "Tokyo Tower")
.observe(.value) { (dataSnapshot) in
print(dataSnapshot)
}
What I want to query is places that the current user visited.
I tried the following. But it does not work.
historiesPlacesRef
.queryOrdered(byChild: "userInfo")
.queryEqual(toValue: Auth.auth().currentUser!.uid)
.observe(.value) { (dataSnapshot) in
print(dataSnapshot)
}
Firebase queries can only check for values that are at a known location under each child node of the location that you query. So in your current structure you can find all places with a specific name, or al the times a specific user went to Tokyo Tower in 2020. But you can't find all users across all cities, nor search all visits for a city (thanks to the Auto ID).
In other words: your current data structure makes it easy to find all users who went to a city you queries for, but it doesn't allow you to easily query for all cities a user went to. To allow this use-case, you'll need to add an additional data structure.
UserPlaces: {
UID1: {
placeID1: {
dateAdded: "2020-01-05",
interactionType: "placeTap"
}
placeID2: {
dateAdded: "2020-01-04",
interactionType: "somethingElse"
}
}
UID2: {
placeID1: {
dateAdded: "2020-01-05",
interactionType: "somethingElseAgain"
}
}
}
With this additional structure (often called an inverted index), you can then look up (or query) data for a specific user. You may need to change this data structure for your specific use-cases, or even add multiple such structures to meet all use-cases.
Also see my answers here:
Firebase query if child of child contains a value
Firebase Query Double Nested

Swift Firebase - getting multiple uid's then adding those uid's to a child

I've been thinking about this but I thought I'd post a question to get some more thinking power behind this or to see if this is even possible. I am grabbing multiple uid's and then want to take these uid's and append them to a child in my database and then add further data to them. Since they are uid's I can't access them separately which would be a easy firebase "update values" call, so how could I take this list of uid's and then add them to a child so they are their own separate children and then add values to them? I am just thinking about how I would set this firebase call to say "add each one of these uid's as its own child".
How I am getting the uid's
func getEmployees() {
let employees = Database.database().reference().child("Businesses").child(self.otherUser?["uid"] as! String).child("registered_employees").observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
let employess = snapshot.childSnapshot(forPath: "uid")
print(employess)
} else {
print("didnt call right values")
}
})
}
sample of data I would add to uid child
let userMessageRef = Database.database().reference().child("user-messages").child(fromID).child(toID)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId!:1])
The code right above ^^^^ I would want the uid's to be in "toID" and then adding the "messageId" to those uid's
I don't know how I could even do each uid separately in the call because of the inability to extract each one and then set the data.
I think I understand so let me try an answer with an example. How we obtain the uid's we want to write is not outlined in the question so let try this:
Suppose we have a users node that stores our users and if they like pizza
users
uid_0 //the Firebase generated uid
name: "Richie"
likes_pizza: true
uid_1
name: "Marion"
likes_pizza: false
uid_2
name: "Fonzi"
likes_pizza: true
uid_3
name: "Howard"
likes_pizza: false
what we want to do it to get the users that like pizza, craft a new node and store each of the uid's as a parent and then a child of their name.
let usersRef = self.ref.child("users")
let pizzaQueryRef = usersRef.queryOrdered(byChild: "likes_pizza").queryEqual(toValue: true)
pizzaQueryRef.observeSingleEvent(of: .value, with: { snapshot in
guard let allUsers = snapshot.children.allObjects as? [DataSnapshot] else {return}
for user in allUsers {
let key = user.key
let name = user.childSnapshot(forPath: "name").value as! String
let pizzaRef = self.ref.child("pizza_lovers")
let aPizzaLoverRefUid = pizzaRef.child(key).child("their_name")
aPizzaLoverRefUid.setValue(name)
}
})
so this code queries for all users that like pizza (which enables us to access their uid's), and then (per the question) append them to a child in the database and then add further data to them
and then want to take these uid's and append them to a child in my
database and then add further data to them
the result is
pizza_lovers
uid_0
their_name: "Richie"
uid_2
their_name: "Fonzi"
Let me know if I misunderstood the question and I will update.

How to observe all firebase database events between two users at concurrent time?

First off, if you have a suggestion for a better title or actual question for this submission, please feel free to edit. I'm stuck as to how to succeed in asking this question.
So I've made gone through several Firebase chat (iMessage/ Facebook chat) tutorials for swift. I know how to send a message -
let ref = Database.database().reference().child("Message")
let childRef = ref.childByAutoId()
let toID = finalSelected.ContactID as Any
let fromID = Auth.auth().currentUser?.uid
let values = ["Message": messageTextField.text!, "toID": toID, "fromID": fromID!] as [String: Any]
childRef.updateChildValues(values) { (error, ref) in ...
and I know how to retrieve them -
let messagesOf = Auth.auth().currentUser?.uid
let messageDB = Database.database().reference().child("Message")
let userMessages = messageDB.queryOrdered(byChild: "toID").queryEqual(toValue: messagesOf)
userMessages.observeSingleEvent(of: .childAdded, with: { (snapshot) in
let values = snapshot.value as! Dictionary<String, String>
let message = values["Message"]
let from = values["fromID"]
let post = ChatMessages()
post.aMessage = message!
post.Interested = from!
self.messagesArray.append(post)
self.tableView.reloadData()
})
However, I'm having a difficult time finishing the logic. I don't understand how these two separate events combine into one identical end result - not two different transactions. Let me see if I can explain it further...
I send a message. I then receive this message as another user. But I do not understand how the to/from data is downloaded that references both simultaneously. Unless I'm looking overlooking some detail, doesn't the single or plural observation of an event only apply to one user? Or am I misunderstanding some concept here?
Help with this final concept would be fantastic. Thank You.
You now have a single list of chat messages, which is very similar to how you'd model this in a relations database. But Firebase is a NoSQL database, so you have better options for modeling chat.
The most common solution is to model chat rooms into your database. So if two users are chatting, then the messages between those two users will be in a "room node". And if two other users are also chatting, their messages will be in a separate room. The data model for this will look like:
chats: {
roomid1: {
messageid1: {
fromId: "UidOfSender1",
message: "Hello there user 2!"
},
messageid2: {
fromId: "UidOfSender2",
message: "Hey user 1. How are you?"
}
},
roomid2: {
messageid3: {
fromId: "UidOfSender2",
message: "Hi mom. Are you there?"
},
messageid4: {
fromId: "UidOfSender3",
message: "Hey there kiddo. Sup?"
}
}
}
So in here we have two chat rooms, the first one between user 1 and 2, and the second between users 2 and 3. If you think of your favorite messaging application, you can probably see how the rooms very directly map to the conversations you see.
Also note that you only need to keep the sender ID. The recipient(s) are are simply everyone else who is in this chat room.
That will likely be your next question: how do I know what rooms a user is in. To determine that, you'll want to keep a separate list of room IDs for each user:
userRooms: {
UidOfSender1: {
room1: true,
room2: true
},
UidOfSender2: {
room1: true
},
UidOfSender3: {
room2: true
}
}
So now you can see for each user in what rooms they are a participant. You can probably again see how this maps to the list of conversations when you start your favorite messaging app. To efficiently show such a list, you may want to keep some extra information for each room, such as the timestamp when the last message was posted.
Generating the room IDs is another interesting aspect of this model. In the past I've recommended to use the UIDs of the participants to determine the room ID, since that leads to a consistent, repeatable room ID. For an example if this, see http://stackoverflow.com/questions/33540479/best-way-to-manage-chat-channels-in-firebase.
If you're new to NoSQL database, I recommend reading NoSQL data modeling. If you come from a SQL background, I recommend watching Firebase for SQL developers.
var newthing = "\(CurrentChatUserId) & \(otherDude)"
Ref.child("posts").child(newthing).queryOrderedByKey().observe(.childAdded, with: {snapshot in
if let dict = snapshot.value as? [String: AnyObject]
{
let mediaType = dict["MediaType"] as! String
let senderId = dict["senderId"] as! String
let senderName = dict["senderName"] as! String
self.obsereveUsers(id: senderId)
let text = dict["text"] as! String
self.messages.append(JSQMessage(senderId: senderId, displayName: senderName , text: text))
self.collectionView.reloadData()
}
})
You could do something like this, so in the database you have different sections for each conversation and at the start get all past messages, and get new ones when a new one is added.

Pulling Data from Firebase into Array

I was recently told to structure my Firebase differently. Before I was putting everything related to a particular user under his or her tree. I was told however to flatten it and create his or her nodes separately and then to just link that node into that users tree when you need to.
So my tree looks like this
root
card
*card autoID*
nickname: "foo"
type: "bar"
user
*user uid*
card
*card autoID*: true
I am going to add more to the card as the user progresses through the app, and if I understand how I am supposed to structure the data I will be adding it to the the card node since that card is linked to the user.
My question is how do I pull data from Firebase then into say an array or a dictionary? If it was all in one tree I would do something like this
let ref = FIRDatabase.database().reference()
let user = FIRAuth.auth()?.currentUser
let userCard = ref.child((user?.uid)!).child("card")
But since that card under the user is only a reference how do I then go to the real place where the card is...the part that has the nickname and type?
Edit
So with some help from other SO posts, the documentation, and a friend I have the code 90% working.
What I am able to do is
1) find all of the card autoID under the user node that is associated to the user and store those strings into an array # 1
2) I am able to query all of the card autoID under the node card and then find the ones that match what is in array # 1 and store them in array # 2 (the rest are ignored)
3) **Here is where I am stuck. If I am inside of the .observe then I can do what I want with the array like printing its contents. HOWEVER, if I call print outside of the .observe I get nothing...
here is my code
func pullCurrentUserCardInfo() {
let userCardsRef = ref.child("users").child((user?.uid)!).child("cards")
userCardsRef.observeSingleEvent(of: .value, with: {(snapshot) in
if let snapDict = snapshot.value as? [String: AnyObject] {
for each in snapDict {
self.usersCardRefArray.append(each.key)
self.count = Int(snapshot.childrenCount)
}
}
})
self.ref.child("cards").observe(.value, with: { (snapshot) in
if snapshot.hasChildren() {
for item in snapshot.value as! [String: AnyObject] {
for test in self.usersCardRefArray {
if test == item.key {
self.allCurrentUsersCards.append(item.key)
}
}
}
} else {
print("no children")
}
})
}
if I were to say the following inside of the function but outside of the .observe ....}) then it doesn't do anything.....
for item in allCurrentUsersCards {
print(item)
}
Am I missing something small somewhere or is this something to do with firebase?
I think there's an unneeded level of complexity here. You do not need to store (in this use case at least) a separate card for each user. There's a 1-1 relationship between user and card so just storing the card data for each user within the user node would be the best answer.
However, to answer the question directly, here's how to do it. We going to slightly alter the Firebase structure:
root
cards
*user uid* <- CHANGE
nickname: "foo"
type: "bar"
users
user uid: true <- CHANGE
Since user uid's are always unique and created for you, leverage them when working with users. So in this case just store the user uid's in the user node and that same uid in the cards node.
Create a User Class and an array to store them in. This would typically be done right inside a viewController for example
class ViewController: UIViewController {
class UserClass {
var uid = ""
var nickname = ""
var type = ""
}
var usersArray = [UserClass]()
Then, craft a Firebase observer to populate the usersArray, getting each card for each user
//iterate over all of the users, get the user and its card data
let usersRef = ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for snap in snapshot.children { //iterate over all users
let userSnap = snapshot as! FIRDataSnapshot
let userKey = userSnap.key //the uid of each user
//now that we have the uid, get it's card data
let thisUserCardRef = cardsRef.child("uid")
thisUserCardRef.observeSingleEvent(of: .value, with: { userSnap in
let userCardSnap = userSnap as! FIRDataSnapshot
let userCardDict = userCardSnap.value as! [String:AnyObject]
let nickname = userCardDict["nickname"]
let type = userCardDict["type"]
let aUser = UserClass()
aUser.userKey = userKey
aUser.nickname = nickname
aUser.type = type
self.usersArray.append(aUser)
//In general, this is where the tableView is refreshed
// because the user data and card data is valid at this point
//usersTableView.reload data /
})
}
})
The key here is to remember that Firebase is asynchronous and that code is way faster than the internet. So this high level example will fail most of the time
func getData() {
loadDataFromFirebase()
print("data loaded, do something with it") //<- executes before the prior line completes
}
func loadDataFromFirebase() {
someRef.observeEvent...
print("data is now valid inside observe closure")
}
This will usually result in
data loaded, do something with it
data is now valid inside observe closure
Which is opposite of what is wanted. Code executes faster than the internet so the asynchronous observe closure will occur after the data loaded... is printed. Only reference and work with firebase data inside a closure and use the closure to pace your app.
If you notice in the first example code provided - we only work with the data once it's been returned from Firebase.
Also note that we completely eliminated queries! Queries are 'heavy' by comparison to observe events and since we are leveraging the uid of each user, it's path will be known, hence the change from a node created with childByAutoId to using the uid.

Firebase Query not Executing Properly

I am trying to execute a Firebase query and it doesn't seem to be working properly. I am trying to access the name of a School I have in the Firebase Database. The database looks like this:
Schools {
Random School Name {
schoolLocation: Random Location
uid: random UUID
}
}
If I use the following code to get the info of the "Random School Name", I get a snapshot of null:
databaseReference.child("Schools").queryEqualToValue("Random School Name").observeSingleEventOfType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
print(snapshot)
}) { (error: NSError) in
self.displayError("Search Failed", message: "We couldn't search for that - \(error.code): \(error.localizedDescription)")
}
If I use this line of code, however, the query gives me the name of the school back, as I would expect. I want to be able to track if there is an error using observeSingleEventOfType:
let query = databaseReference.child("Schools").queryEqualToValue("Florida Institute of Technology")
print(query)
Why isn't this working?
I think you're querying by priority here, which will only work if your schools have a priority (highly unlikely).
If that is indeed the problem, solve it by being explicit about the ordering: queryOrderedByKey(). Then also be sure to loop over the children in the result, since a query will return a list, even if there's only one result:
databaseReference
.child("Schools")
.queryOrderedByKey()
.queryEqualToValue("Random School Name")
.observeSingleEventOfType(.Value, withBlock: { snapshot in
for childSnapshot in snapshot.children {
print(snapshot)
}
})
Hey Dan you should try this query for retrieving specific data
self. databaseReference.queryOrderedByChild("Schools").queryEqualToValue("Random School Name").observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.value)
})
This query returns the random school name for the key schools

Resources