querying firebase efficiently - ios

I want to search my user base from each users name value. From what I've seen online people often return all users then filter them in a table view but that doesn't seem practical nor viable. My thought was to query data and return an exponentially smaller array of values but I am having trouble using the query methods provided.
How do I query a specific aspect of my database?
How do I structure my code so that it's viable; not loading in EVERY user, something like 10 max at a time.
Any suggestions, resources, and links are greatly appreciated.
EDIT:
I did some researching and it looks like Firebase comes with some built in querying methods... So far this is what I'm attempting to test it out with the code below to print out users starting with I, but I can't get it to print any users in the console
ref.queryOrderedByKey().queryStarting(atValue: "I").queryEnding(atValue: "I\u{f8ff}")
.observe(.childAdded, with: { snapshot in
print(snapshot.key)
})

There are a number of solutions and often times loading ALL of the user data is too much data.
Here's a typical users node
users
uid_0
name: "Jean Luc"
uid_1
name: "Will"
uid_2
name: "Geordi"
One option is to iterate through each user node, one at a time, to retrieve the user name. This avoids an enormous data set entirely. We'll use the .childAdded event to load each and store in an array
let usersRef = self.ref.child("users")
var userNamesArray = [String]()
usersRef.observe(.childAdded, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let name = userDict["name"] as! String
userNamesArray.append(name)
})
A second option is to store the user name in an entirely different node, which significantly reduces the 'clutter' as the rest of the data remains in the main users node
user_names
uid_0: "Jean Luc"
uid_1: "Will"
uid_2: "Geordi"
As you can see with this structure, even with thousands of names it's just text with a very small footprint.
Another option is to load X number of users at a time using .startingAt and .endingAt and iterate over the returned users to get each name. In this case we want all users starting with A and ending with M... Sorry Worf.
let usersRef = self.ref.child("users")
var userNamesArray = [String]()
let nameQuery = usersRef.queryOrdered(byChild: "name")
.queryStarting(atValue: "A")
.queryEnding(atValue: "M\u{f8ff}")
nameQuery.observe(.value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let userDict = snap.value as! [String: Any]
let name = userDict["name"] as! String
userNamesArray.append(name)
}
})
The last example started with users names starting with A and ended with user names ending with M + a very high unicode character, which makes it inclusive for all names starting with M
The \uf8ff character used in the query above is a very high code point
in the Unicode range. Because it is after most regular characters in
Unicode, the query matches all values that start with queryString.

Related

Firebase Realtime Database querying not working

I'm trying to retrieve some values instead of others. In fact, I want to search for users by their names.
I've tried using queryStarting(atValue: input)and queryEnding(atValue: "u{f8ff}") but I couldn't find the right solution.
let input = searchInput.text?.lowercased()
print("Search for \(input!)")
let ref = Database.database().reference().child("Usernames").queryStarting(atValue: input).queryEnding(atValue: "u{f8ff}").queryOrderedByKey()
ref.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
for users in snapshot.children.allObjects as! [DataSnapshot] {
let user = users.value as? [String:AnyObject]
let id = user?["id"] as! String
Users.init(userId: (id))
print(users.key)
}
}
}
As you can see, I also added queryOrderedByKey(). If I don't add that part, it doesn't retrieve anything.
What I want is only retrieve the values starting by what I write in the textfield.
Usernames
----- charles
----------id: charles's uid
----- pierre
----------id: pierre's uid
----- bob
----------id: bob's uid
----- lu
----------id: lu's uid
The problem is that for example if the input is 'L' (to search for lu), it retrieves [lu]. Then if the input is 'Bo' (to search for bob), it retrieves [lu] and [bob], not only [bob] as I want. And obviously if the input is 'C' (to search for bob), it retrieves [lu], [bob], [pierre], and finally [charles].
Your current query starts at your input which is correct, but ends at u{f8ff} - meaning if you start bo, you'll also get words starting with c, d, etc.
What you need to do is end at the the highest value that starts with you string, e.g. for an input of bo you would want to end at bo\u{f8ff}. You can do this by concatenating your input with that unicode value, i.e.:
...queryEnding(atValue: input + "\u{f8ff}")... // <-- Also note that the unicode character is escaped here

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.

Firebase Access a children if I don't know two childs who have unknown ids

How can I access the data of the children memberId or name and photoURL of the child "members"?
You can see the structure of my database in images.
I tried to use queryOrdered and queryEqual but I just can use it one time
I tried like that because I know the room.key who is the "key" on the database.
let refParticipants = refDatabase.child("markers").queryOrdered(byChild: "key").queryEqual(toValue: room.key)
refParticipants.observe(.childAdded, with: { snapshot in
...
}
I use Swift 3.1
I update my answer with that screenshot:
I think you are asking how to access the child nodes of
/markers/oHQ.../members/9oBKY...
Let's simply the structure for this answer
markers
marker_0
members
member_0
name: "J"
member)1
name: "K"
and then the code that will access each member within the members node and print their name
let markersRef = self.ref.child("markers")
let marker0Ref = markersRef.child("marker_0")
let membersRef = marker0Ref.child("members")
membersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["name"] as! String
print(name)
}
})
and the output will be
J
K
Since you know the parent node (oHQa...), which contains the child node 'members', it doesn't really matter as to what each members key is since you are iterating over them.
However, if you are wanting to query for certain members or other member data, you may want to consider flattening the database a bit like this
markers
oHQa...
//marker data
marker_members
member_0
name: "J"
member_of: "oHQa..."
member_1
name: "K"
member_of: "oHQa..."
With this structure you can query for all the members of any marker or query for all members whose name is "J" etc.
As a side note, in the structure in the question you have the member_id as both the key as well as a child which is unnecessary. If it's the key then you can always directly access that node without a query.

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.

Resources