Observing data on Firebase vs. checking an array? - ios

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)
}
})
}
})

Related

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.

Firebase observeEventType .childAdded doesn't work as promised

When I call this observe function from in my viewcontroller, the .childadded immediately returns a object that was already stored instead of has just bin added like .childadded would suspect.
func observe(callback: RiderVC){
let ref = DBProvider.Instance.dbRef.child("rideRequests")
ref.observe(DataEventType.childAdded) { (snapshot: DataSnapshot) in
if let data = snapshot.value as? NSDictionary {
let drive = cabRide(ritID: ritID, bestemming: bestemming,
vanafLocatie: vanaf, taxiID: taxiID, status: status)
print(drive)
callback.alertForARide(title: "Wilt u deze rit krijgen?", message: "Van: \(vanaf), Naar: \(bestemming)", ritID: ritID)
}
}
}
When I try this function with .childchanged, I only get a alert when it is changed like it suppose to do, but when doing .chiladded, it just gets all the requests out of the database and those requests were already there.
When I add a new request, it also gives an alert. So it works, but how can I get rid of the not added and already there requests?
Does anybody know this flaw?
This is working exactly as promised. From the documentation:
Retrieve lists of items or listen for additions to a list of items.
This event is triggered once for each existing child and then again
every time a new child is added to the specified path. The listener is
passed a snapshot containing the new child's data.
That might seem weird at first, but this is generally what most developers want, as it's basically a way of asking for all data from a particular branch in the database, even if new items get added to it in the future.
If you want it to work the way you're describing, where you're only getting new items in the database after your app has started up, you'll need to do a little bit of work yourself. First, you'll want to add timestamps to the objects you're adding to the database. Then you'll want to do some kind of call where you're asking to query your database by those timestamps. It'll probably look something like this:
myDatabaseRef.queryOrdered(byChild: "myTimestamp").queryStarting(atValue: <currentTimestamp>)
Good luck!

Cross reference columns in Parse to decide which users to Display on a view controller. Swift, user search settings

What I am trying to figure out is how to only display users that meet the setting requirements that the user previously saved on a different ViewController. Ok so on set up the current user has selected their genre and instrument and it has saved in columns in Parse called "genre" and "instrument" as Strings. Then on the search settings page the user has selected that they would like to search for lets say "Rock" as the genre and "Acoustic guitar" as the instrument. Both of these then get added to Parse under the columns "genreSearch" and "instrumentSearch".
So I know I need to make a query and display it on the ViewController that the users are displayed on but I don't know how. I am trying to basically cross reference the column "genre" of other users against the current users column "genreSearch". I imagined it would be something like this:
genreQuery.whereKey("username",notEqualTo:PFUser.currentUser()!.username!)
genreQuery.whereKey("genre", notEqualTo:PFUser.currentUser()!username!)
genreQuery.whereKey("genreSearch", equalTo:PFUser.currentUser()!)
genreQuery.findObjectsInBackgroundWithBlock { (users: [AnyObject]?,
error: NSError?) -> Void in
if error == nil {
for user in users! {
if self.genre == self.genreSearch {
print("These two strings are considered equal")
appUsers.append(user as! PFUser)
}
self.resultsPageTableView.reloadData()`
At the top of my VC I have as I am storing and displaying all the users in a cell which also links to another VC to show more details.
var genre = [String]()
var genreSearch = [String]()
var appUsers = [PFUser]()
I have read Parse docs and to be honest now I am more confused as where to go.
I have searched the internet for past few days and it is all js and objc both of which I have zero experience in. If someone could point me at a start or even guide me in what to do so I can learn.
In the cell I am displaying the users details like so:
let singleCell: CustomCell = tableView.dequeueReusableCellWithIdentifier("mySingleCellid") as! CustomCell
let userObject = appUsers[indexPath.row] as PFObject
singleCell.userName.text = userObject["name"] as? String
// etc
return singleCell
This part of the query
genreQuery.whereKey("genreSearch", equalTo:PFUser.currentUser()!)
is wrong because you're trying to check that an array of strings contains a user object (a pointer) - which will always fail.
While you have genreSearch on the server for the current user it's easier to just replace that part of the query with
genreQuery.whereKey("genreSearch", containedIn:PFUser.currentUser()!["genreSearch"])
which instead asks for the genreSearch array on each user tested to contain at least one of the current users array of genreSearch
I was going about it all wrong what I needed to do was take genreSearch from the Parse DB and store it in the app itself as a variable. This variable is then a key for my PFQuery and I use it to filter out the people that don't have it. I then use genreSearch as a condition skipping the people that don't have it and adding the people that do. I nearly have it cracked except for the last few coding of it. Instead of editing this question to ask for help I have asked and posted my new code to a new SO question

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

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

Resources