Query users by name or email address using Firebase (Swift) - ios

I'm quite new to Firebase and Swift and I'm having some trouble when it comes to querying.
So there are basically two things I'd like to do:
Query my users and find only those that contain a certain String in their name (or email address) and add them to an array.
Get all of my users and add them to an array.
The relevant part of my data for this question looks like this:
As you can see, I'm using the simplelogin of Firebase (later I'd like too add Facebook login) and I'm storing my users by their uid.
A part of my rules file looks like this:
"registered_users": {
".read": true,
".write": true,
".indexOn": ["name"]
}
So everybody should have read and write access to this part of my data.
I also read the "Retrieving Data" part of the Firebase iOS Guide on their website and according to that guide, my code on getting all the users names and email addresses should work, at least I think so. But it doesn't. Here is my code:
func getUsersFromFirebase() {
let registeredUserRef = firebaseRef.childByAppendingPath("registered_users")
registeredUserRef.queryOrderedByChild("name").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let email = snapshot.value["email"] as? String {
println("\(snapshot.key) has Email: \(email)")
}
if let name = snapshot.value["name"] as? String {
println("\(snapshot.key) has Name: \(name)")
}
})
}
I noticed, that in the firebase guide, they always used the type ChildAdded and not Value, but for me Value makes more sense. The output with Value is nothing and the output with ChildAdded is only one user, namely the one that is logged in right now.
So my questions are:
Can I do this query with my current data structure or do I have to get rid of storying the users by their uid?
If yes, how would I have to change my code, to make it work?
If no, what would be the best way to store my users and make querying them by name possible?
How can I query for e.g. "muster" and get only the user simplelogin:1 (Max Mustermann)?
I hope my description is detailed enough. Thx in advance for your help.
Supplement:
The weird thing is, that the "Retrieving Data" guide says, that querying and sorting the following data by height is possible.
Data:
Querying code:
And isn't that exactly the same that I intent to do?

I have run into similar situations where I wanted to pull out data from child nodes.
https://groups.google.com/d/msg/firebase-talk/Wgaf-OIc79o/avhmN97UgP4J
The first thing I can recommend is to not think of Firebase query's as SQL queries as they are not. They are like a light duty query.
Secondly, you need to flatten your data if you want to query, as a query only goes one level deep (can't really query data in child notes)
Lastly - if you don't want to flatten your data, one conceptual option to answer your question;
If possible, ObserveSingleEventOfType:FEventTypeValue on the
registered users node. This will read all of the registered users into a snapshot.
Iterate over the snapshot and read each user into an array (as dictionary objects)
Then use NSPredicate to extract an array of users that you want.
I've run numerous tests and performance wise, it's negligible unless you have thousands of users.
Hope that helps!

To answer your questions
1) Yes, you can query with your current structure. A query can go 1 child deep, but not within a child's children.
2) If yes, how would I have to change my code, to make it work?
Here's a quickie that queries by a users last name:
Firebase *usersNodeRef = your users node 'registered_users'
FQuery *allUsersRef = [usersNodeRef queryOrderedByChild:#"lastName"];
FQuery *specificUserRef = [allUsers queryEqualToValue:#"aLastName"];
[specificUser observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSDictionary *dict = snapshot.value;
NSString *key = snapshot.key;
NSLog(#"key = %# for child %#", key, dict);
}];
How can I query for e.g. "muster" and get only the user simplelogin:1 (Max Mustermann)?
In your uses node structure, the users are store in nodes with a key.. the key is the simplelogin:1 etc. snapshot.key will reveal that. So it's key/value pair deal...
value = snapshot.value
key = snapshot.key

Related

Get list of users who liked a post Swift and Firestore

I am attempting to build a tableview that displays the users who have liked the current user's posts. For example, "John Smith, Tom Jones, and Sally Hughes liked your post (display image of post in cell". I think I'm on the right track but I'm wondering if my data structure is going to make this unnecessarily difficult or if there is an easier way.
My Firestore data structure is just below. The "BmV..." is the userId, "-M0e..." is the postId, and the "Ff0..." and "nIh..." are the users who have liked the post.
"likeActivity" : {
"BmvRlWWuGRWApqFvtT8mXQlDWzz2" : {
"-M0efUXcZy43fDVXjTvT" : {
"Ff0CxZhQzYVHuqbnsiOKwRAB01D2" : true,
"nIhx1SnChjapy4cbrD5sC1WIZXM2" : true
}
},
}
My first question is if this is the best way to structure this data? Then, In the ActivityViewController using the following code to retrieve of the current user's posts with activity.
var activityDict = [String: [Any]]()
let newActivity = DataService.ds.REF_LIKE_ACTIVITY.child("\(uid)")
//print("NEW POST - \(newPost)")
newActivity.observe(.value, with: { (snapshot) in
self.posts = []
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {
print("ACTIVITY -- \(snap.key)")
let userLikeData = DataService.ds.REF_LIKE_ACTIVITY.child("\(uid)").child(snap.key)
userLikeData.observe(.value, with: { (snapshot) in
self.posts.append(snap.key)
print("SNAPSHOT VALUE -- \(snapshot)")
if self.activityDict["\(snap.key)"] != nil {
self.activityDict["\(snap.key)"]!.append(snapshot.value!)
} else {
self.activityDict["\(snap.key)"] = [snapshot.value!]
}
self.activityTableView.reloadData()
})
}
}
})
My next question is if the activity should be accumulated in an array of dictionaries like I have it? Is there a better way to organize this data?
My first question is if this is the best way to structure this data?
When using NoSQL databases there is no singular "best" way to store data. You'll instead store the data in a way that best supports your use-cases. And since you'll typically uncover more (details about your) use-cases as you implement and evolve your app, you data model evolves (adapting and expanding) with it.
I recommend reading NoSQL data modeling, and watching Firebase for SQL developers and Getting to know Cloud Firestore. The last one is for Cloud Firestore, but many of the techniques Todd discusses apply equally to other NoSQL databases.
should [the activies] be accumulated in an array of dictionaries like I have it?"
If that works for your use-cases, then it sounds fine.
The only thing of note is that you're using observe, which attaches a permanent listener, and then append the updated snapshot data to the array. This means that if an activity gets changed in the database, your closure will get called again with a snapshot for that activity.
This is great, because it allows you to show the updated activity in the UI. But since you're appending it to the array, you'll end up displaying the activity twice: once as it was stored in the database when you first loaded it, and then again as it exists after the update. If that is what you're aiming for then 👍, but it is more common to update the existing data in the array, instead of adding the updated data as a new item.

How can I access firebase directory names as an array?

I'm trying to access department and course information for an iOS app for students to buy/sell textbooks.
Right now I have two pickerViews. I'd like to select the department in one and have the relevant courses load into the second. What kind of call can I make to get an array of just the department names when structured as below?
So here I would want to access an array ["AHSS", "AIE", "ANTH"]. Then afterwards, I'd make another call depending on the selection. For AIE, I'd want the array ["330", "340"].
I'm unsure how I can just get the directory names as an array and not the values they eventually lead to?
For anyone else that might have had this question. #Nikunj Kumbhani directed me in the right direction and I eventually got this which lists the key values.
myDatabase.child("departments").child("\(selectedDepartment ?? "AHSS")").observeSingleEvent(of: .value) { (snapshot) in
if let myData = snapshot.value as? NSDictionary {
for name in myData.keyEnumerator() {
stringList.append("\(name)")
}

Remove an entire child from Firebase using Swift

I'd like to write a function to remove an entire child from Firebase. I'm using XCode 10 and swift 3+.
I have all the user info of the child I'd like to delete so I assume the best call would be to iterate through every child and test for the matching sub child value but it would be great if there was a faster way.
Thanks for the help!
Heres what I'd like to delete
I assume testing for epoch time then removing the whole node would be ideal. Also not sure how to do this
I understand you don't have access to the key of the node you want do delete. Is that right? Why not? If you use the "observe()" function on a FIRDatabaseQuery object, each returned object should come with a key and a value.
Having a key it is easy to remove a node, as stated in the Firebase official guides.
From the linked guide,
Delete data
The simplest way to delete data is to call removeValue on a reference
to the location of that data.
You can also delete by specifying nil as the value for another write
operation such as setValue or updateChildValues. You can use this
technique with updateChildValues to delete multiple children in a
single API call.
So, you could try:
FirebaseDatabase.Database.database().reference(withPath: "Forum").child(key).removeValue()
or
FirebaseDatabase.Database.database().reference(withPath: "Forum").child(key).setValue(nil)
If you can't get the key in any way, what you said about "iterating" through the children of the node could be done by using a query. Here's some example code, supposing you want all forum posts by Jonahelbaz:
return FirebaseDatabase.Database.database().reference(withPath: "Forum").queryOrdered(byChild: "username").queryEqual(toValue: "Jonahelbaz").observe(.value, with: { (snapshot) in
if let forumPosts = snapshot.value as? [String: [String: AnyObject]] {
for (key, _) in forumPosts {
FirebaseDatabase.Database.database().reference(withPath: "Forum").child(key).removeValue()
}
}
})
Here you create a sorted query using as reference "username" then you ask only for the forum posts where "username" are equal to Johanelbaz. You know the returned snapshot is an Array, so now you iterate through the array and use the keys for deleting the nodes.
This way of deleting isn't very good because you might get several posts with the same username and would delete them all. The ideal case would be to obtain the exact key of the forum post you want to delete.

Observing data on Firebase vs. checking an array?

I've got a situation where I've got a tableview being filled with names from Firebase.
When the view loads in, I pull all the necessary names from firebase, load them into an array, and base my tableview off that array.
I have an "add" button that takes whatever's in a text field and adds that name to both firebase and their list.
What I do not want to allow is for people to add a name that they have already added.
I'm pulling the names they've added from Firebase like:
users
209384092834
Names
Bob
Sue
so if the user were to type in Rob, it'd add it under that "names" bit, but if they typed in Bob/Sue it wouldn't allow them to add that again.
The two ways I see of doing this are to check if the name a user is wanting to add is in the array I'm filling on load, or to check against the names that are under their Names child on firebase.
Are there any strong arguments for using one over the other. Is it a "big deal" to run an observer to firebase? I feel like using firebase here is "safer" than checking the array..like what if the users net is so or inturrupts, the array hasn't filled up yet, and they type in a name to add, add it, and everything is just a mess. I don't even know if something like that COULD happen.
Any advice here on which direction to take and why?
Important :- Never use Arrays or Tuples to store in Firebase Database, always prefer Dictionary
Make your DB look something like this :-
{ users : {
209384092834 : {
Names: {
Bob : True,
Sue : True
}
}
}
}
I would suggest you use a third path :-
Check if that name exists by referring to that child node and checking by taking a particular snap of that path instead of the entire list..
rootRef.child("users").child(timeStamp).child("Names").child(textField.text!).observeEventType(.Value, withBlock: {(snapshotRecieved) in
if snapshotRecieved.exists()
{
//Show Alert that user Exists i.e if user is rob/sue in your case
}else{
let ref = rootRef.child("users").child(timeStamp).child("Names")
ref.observeEventType(.Value, withBlock: {(snapshot) in
if let namesDict = snapshot!.value as? NSMutableDictionary{
namesDict.setObject("True",forKey:textField.text!)
ref.setValue(namesDict)
}
})
}
})

Firebase fetch multiple results

I was wondering if there is any way with swift and firebase, to get multiple numbers from the same child? and average all the numbers.
Example,
Every authorized user saves a number to the database. Then when they look at there number in a saved section it averages all other users numbers and compares it against theres.
I can get all the data to save, and I can get the data to send back info, but it only sends one number from the list
I have tried different ways but I can't get it to work. Any help would be awesome.
I would post some code but none of it is working real well
Thanks again in advance
For your example, you would use childByAutoID to create a random unique identifier for your user. To get multiple values from one child you would use a comma, when setting the childs value. Like this,
struct getInput {
var inputType: String!
}
//copying and pasting this code into your code will not work, however
//you should be able to understand why and how this works.
let getUsername = getInput(inputType: usernameTextField.text!)
let getPassword = getInput(inputType: passwordTextField.text!)
let userReference = database.reference().child("users")
userReference.setValue(getUsername.inputType)
let childByAutoID = userReference.childByAutoId()
childByAutoID.setValue(["username": getUsername.inputType, "password": getPassword.inputType])

Resources