Firebase Realtime Database querying not working - ios

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

Related

Why is firebase function only fetching once?

I have a firebase fetch function which I call inside of a for loop. In it, I pass in variables postID and uid.
for child in snapshots.reversed() {
let keyValue = child.key
let uid = keyValue.split(separator: ":")[0]
let postIDDoubleVal = keyValue.split(separator: ":")[1]
print(String(uid), " This is the uid!!!!!!")
print(postIDDoubleVal, " This is tfdsafdsafdsafdsafads4!!!!")
self.fetchUsersPost(uid: String(uid), postID: "post:\(postIDDoubleVal)")
}
There are currently 2 users who's UIDs ARE successfully looped over.
The problem arrises when calling the function fetchUsersPost. For some reason for one of the loops (I believe the second) it works properly, but for the first it does not.
The beginning of the fetch function is:
func fetchUsersPost(uid: String, postID: String) {
print("fetchUsersPost Posts/\(uid)/\(postID)")
Here is the output:
fetchUsersPost Posts/QUocyvGehdeaOO9vVnklwOrWH7l1/post:580077760
QUocyvGehdeaOO9vVnklwOrWH7l1 This is the uid!!!!!!
580077723 This is tfdsafdsafdsafdsafads4!!!!
fetchUsersPost Posts/ QUocyvGehdeaOO9vVnklwOrWH7l1/post:580077723
I wonder if the problem is related to the fact that on one of the print statements (the second) there is a space between the uid and the '/'
What is the problem?
You have 1 user with UID = QUocyvGehdeaOO9vVnklwOrWH7l1 who has 2 posts with 580077760 and 580077723 , if there is a space you need to verify that you create the child key correctly when you set the value for it
let keyValue = child.key
as UID:POSTID with no leading , trailing or middle spaces , also make sure whether these 2 posts have a valid value before you fetch them

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.

querying firebase efficiently

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.

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.

Get the key from last child in Firebase

I need to be able to select randomly some data from my database on Firebase. For that and in order to avoid to download the whole database, I want to create entries with a key represented by an unsigned int like on this screenshot:
My question is, how can I retrieve the key of the last item added?
I tried:
ref.queryOrderedByKey().queryLimited(toFirst: 1).observeSingleEvent(of: .value, with: { [unowned self] snapshot in
let id: UInt64
if snapshot.exists(), let child = snapshot.children.nextObject() as? FIRDataSnapshot {
id = UInt64(child.key)!
} else {
id = 1
}
})
but UInt64(child.key)!, whether I use queryLimited(toFirst:) or queryLimited(toLast:), always returns 1.
What do I do wrong?
Your query returns the first result, which has key 1. You're looking for queryLimited(toLast: 1).
Additionally, in most cases, you should avoid numeric, sequential keys in distributed data.

Resources