Swift: Find whether or not a member exists in Firebase Database - ios

I'm new to Firebase and pretty new to Swift, and I'm finding that I'm having trouble working with query's to retrieve data.
My goal is the following:
To find which groups a specific user is a member of
I tried to flatten the data by creating an object of only a group's members (using it's AutoId key).
{ groupMembers:
-KM0fTdaN2D_BKkvlk34:
user1: true
user2: true
user3: true
}
-KM4ZaXCvALCV_gN74U8: {
user3: true
user4: true
}
}
I tried to use the following code to run through a list of users, trying to only pull the relevant groups. Unfortunately I'm getting an InvalidQueryParameter error.
let groupMemberRef = rootRef.child("groupMembers")
func userFilter(list: [String]) {
// list is a list of different users (strings) I'm looking through the groups to try to find
for user in list {
groupMemberRef.queryEqualToValue(true, childKey: user).observeSingleEventOfType(.ChildAdded, withBlock: { (snapshot) in
print(user)
print(snapshot)
print(snapshot.hasChild(user))
})
}
Am I even going in the right direction? I've tried dozens of other strategies for chaining the methods without any luck. Thank you for any help - I really a truly appreciate it!!

You need to store a list of group id's under each user, keeping track of the groups each use is in.
Your DB structure should be something like:
Users: {
User_id_1: {
Name: "user name",
...
Groups: {
group_id_1: true,
group_id_2: true,
...
}
}
},
Groups: {
group_id_1: {
user_id_1: true,
...
}
}

Related

Firebase iOS -What is the difference between .queryOrdered().queryEqual(toValue: and child().observeSingleEvent

In Firebase when searching for a username (or anything search related for that matter) what is the difference between
.queryOrdered().queryEqual(toValue: and child().observeSingleEvent(
I looked at the answer and comments below from #Frank van Puffelen
In these comments he says to use:
reference.child("usernames").child(usernameYourSearchingFor).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() { ... } else { ... }
})
But in this answer he answered with:
ref.child("users").queryOrdered(byChild:"username").queryEqual(toValue: usernameYourSearchingFor).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() { ... } else { ... }
})
When searching the database using either of these methods, is there a difference between speed and efficiency, or is it simply a matter of using 2 different methods to achieve the same exact goal?
Basically, those operations are meant for two different data structures.
In the first one, you'd have a data structure similar to this:
{
"usernames": {
"usernameYourSearchingFor": {
// some data
},
"otherUsername": {
// different data
}
}
}
As you can see, the username you're searching for is a key under the "usernames" node. You can access it directly, by calling the child() function. (and then a single event observer is attached to it)
Now in the second structure:
{
"users": {
"user1": {
"username": "usernameYourSearchingFor",
"email": "user1#email.com"
},
"user2": {
"username": "otherUsername",
"email": "user2#email.com"
}
}
}
The username is actually an attribute belonging to the User object. There's no way you can access it directly, so you need to query the "users" node in order to find the username you're searching for, hence the need of .queryOrdered(byChild:"username").queryEqual(toValue: usernameYourSearchingFor).

Cloud Firestore Swift: How to delete a query of documents

I would like to delete all documents in my collection Usernames that has the field UID as the current user's ID. This is my code so far:
let uid = Auth.auth().currentUser!.uid
db.collection("Usernames").whereField("UID", isEqualTo: uid).delete
but here comes the error:
Value of type 'Query' has no member 'delete'.
Any special technique to this? Thanks!
The guide shows you how to delete data. It also notes that deleting entire collections shouldn't be done from the client. There's no way to delete from a query -- you will have to get all the documents and delete them individually. If there is the potential for lots of documents, then you should do this server-side. If you know for sure there will only be a few, you can query them, get the doc references, and then delete them, like this:
let uid = Auth.auth().currentUser!.uid
db.collection("Usernames").whereField("UID", isEqualTo: uid).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
document.reference.delete()
}
}
}
If this is something that will need to be done frequently, then this is a good opportunity to examine your data structure. Maybe make a collection where user's Usernames are listed under their uid. Then you could just query that single doc and use it to reference the other ones to delete.
Usernames: {
byUID: {
uid1: {
funky_monkey: true,
goodTimes: true
}
}
byUsername: {
funky_monkey: {
UID: uid1
}
}
}
This is just one suggestion. There are definitely other options that may be better for your app.

Listing Firebase data and relationships

I have just started working with Firebase database and I am a bit confused how to structure the database. In the following example I have a users object and a groups object. Each user can be part of multiple groups and every group can have multiple users. The proposed database structure is the following, according to "Structure Your Database".
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers": true,
"womentechmakers": true
}
}
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"startDate": "24-04-1820",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
}
}
}
Let's say I want to display all groups in a list in my app, with the group name and start date. How would I make that database call? Since the user object only contains the id of the groups, would I then have to make a separate call to the database for every group just to find out the name and start date of the group? If there are many groups in the list, then that becomes a lot of calls. My group might contain a lot of other information as well so this doesn't seem good for performance. Can I get all the groups in the groups list of the user, in one call?
One though I had was to include the name and start date in the groups object under the user:
"users": {
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers":{
"name": "Historical Tech Pioneers",
"startDate": "24-04-1820"
},
"womentechmakers":{
"name": "Women in Technology",
"startDate": "13-10-1823"
}
}
}
}
}
but this solution seems to add a lot of duplicate data. Also if I want to update the name I would have to do that in multiple locations. And maybe I want to add a sponsor organization object, that also contains group, and then want to list them. Then there would be 3 places to update the information on. How would I solve this?
You would then have two possibilities, one would be to store the data you need (duplicating it) in the groups node of each user.
The other, which is the one that I would recommend the most, would be to add an observeSingleEvent(of: .value) inside your first observer (that could be an observe(.value), observe(.childAdded) or whatever).
Say you have an array of all your group members, and an object named AppUser that represents a user in your app :
var groupMembers = [AppUser]()
To detect whenever a new member is added to a group for example, you could use a .childAdded observer for example :
func groupWasAddedObserver(completion: #escaping () -> ()) {
// Add a .childAdded observer to the group's members node (groupId should be defined somewhere) :
groupsRef.child(groupId).child("members").observe(.childAdded, with: { [weak self] (snapshot) in
// When you get the snapshot, store its key, which represents the member id :
let memberId = snapshot.key
// fetch this member's profile information using its id :
self?.getUser(memberId, completion: { (groupMember) in
// Once you got the profile information of this member, add it to an array of all your members for example :
self?.groupMembers.append(groupMember)
// Call the completion handler so that you can update the UI or reload a table view somewhere maybe depending on your needs :
completion()
})
})
}
And the second method to fetch a user data knowing his or her id :
func getUser(_ userId: String, completion: #escaping (AppUser) -> ()) {
// Add the observerSingleEvent observer :
usersRef.child(userId).observeSingleEvent(of: .value, with: { (snapshot) in
// Get the data you need using the snapshot you get :
guard let email = snapshot.childSnapshot(forPath: "email").value as? String else { return }
guard let name = snapshot.childSnapshot(forPath: "name").value as? String else { return }
guard let picUrl = snapshot.childSnapshot(forPath: "picUrl").value as? String else { return }
// Call the completion handler to return your user/member :
completion(AppUser(id: snapshot.key, email: email, name: name, picUrl: picUrl))
})
}
As you can see you get the memberId of each user using the snapshot key, and you use this memberId to fetch this specific user data.

will my structure for database in firebase affect the speed of loading?

I am using firebase as backend for my app. My app will be some sort of expense tracker. The way that I am thinking I should restructure my data so that I can filter the dates easily is as such.
uid/expenses/category/year/month/day/uniquekey/details
to dig into the snapshots would be something like this
func retrieveData (){
_ = dataRef.observeEventType(.ChildAdded, withBlock: { (snapshotOne) in
if let snapshotTwo = snapshotOne.children.allObjects as? [FIRDataSnapshot] {
if let snapshotThree = snapshotTwo.children.allObjects as? [FIRDataSnapshot] {
if let snapshotFour = snapshotThree.children.allObjects as? [FIRDataSnapshot] {
for item in snapshotFour {
//retrieve details
}
}
}
}
}
}
I have no idea what the consequences are to have to dig so deep into the database and whether my structure for database is okay? could anyone advise?
Any Change to Data structure will effect your database.
Firebase prefers Flattened Data
& Using Indices to Define Complex Relationships:
A lot of times in building apps, it's preferable to download a subset of a list. This is particularly common if the list contains thousands of records or more. When this relationship is static, and one-directional, we can use queries to grab a subset of data, or simply nest the entries under the logical grouping.
{
"messages": {
"john": {
"rec1": "Walk the dog",
"rec2": "Buy milk",
"rec3": "Win a gold medal in the Olympics"
}
}
}
However, we already know that flattening data is a best practice. So let's see why, by examining where this structure begins to break down. If we move into something more dynamic, like shared chat rooms, then suddenly our data (e.g. lists of rooms, lists of messages) now have two-way relationships.
Users can belong to a group and groups comprise a list of users. A first attempt at resolving this data structure would probably look this:
{
"users": {
"mchen": { "name": "Mary Chen" },
"brinchen": { "name": "Byambyn Rinchen" },
"hmadi": { "name": "Hamadi Madi" }
},
"groups": {
"alpha": {
"name": "Alpha Tango",
"members": {
"m1": "mchen",
"m2": "brinchen",
"m3": "hamadi"
}
},
"bravo": { ... },
"charlie": { ... }
}
}
You can read more here.
https://www.firebase.com/docs/web/guide/structuring-data.html
The whole structure of your database is related to the data you structure to store.
Also consider reading more about the Security and Rules.
https://www.firebase.com/docs/security/quickstart.html

Firebase Object Ownership with Event Observation

I'm using Firebase in my iOS app. I'd like each of my objects to have a creatorId property whose value is the authenticated user ID (authData.uid with a Firebase authentication object). I'm using a custom token generator for authentication, but the problem can be reproduced with anonymous log in too.
I'd like a user to only be able to read (and write, but let's focus on reading right now, as that's where I'm having my issues) objects that they created. In other words, the querying user's authenticated user ID will match the creatorId of the objects they are fetching.
I'm having trouble with permissions when I craft queries and rules to make this happen.
Here is the Firebase documentation for Rules and Security.
Here is what my Firebase dashboard looks like for a Task object:
+ firebase-base
+ tasks
+ {task_id}
+ creatorId:
+ title:
where task_id is a unique identifier generated by Firebase upon insertion.
My rules look like this (again, let's ignore writing rules for now):
{
"rules": {
"tasks": {
"$task_id": {
".read": "auth.uid === data.child('creatorId').val()"
}
}
}
}
Reading a specific task works fine, but I'd expect to be able to make a query that says, "fetch all the tasks that I created" using observeEventType and related functions. This doesn't work for me. I get "Permission Denied" errors.
Here is how I'm observing, in Swift:
let reference = Firebase(url: "https://{My-Firebase-Base-Reference}/tasks")
reference.observeEventType(.ChildChanged,
withBlock: { (snapshot: FDataSnapshot!) -> Void in
// Success
}) { (error: NSError!) in
// Error: I get Permissions Denied here.
}
Per #Ymmanuel's suggestions, I also tried being more specific in my query, like so:
let reference = Firebase(url: "https://{My-Firebase-Base-Reference}/tasks")
reference.queryOrderedByChild("creatorId").queryEqualTo({USER_UID}).observeEventType(.ChildChanged,
withBlock: { (snapshot: FDataSnapshot!) -> Void in
// Success
}) { (error: NSError!) in
// Error: I get Permissions Denied here.
}
Neither of these blocks work, I always get "Permission Denied" errors. What am I doing wrong?
What you are missing is that you are assuming that security rules are queries and that is not true.
Check the
Rules are Not Filters section in the link.
Security rules only validate if you can read or write a specific path of your firebase database.
If you want to only receive changes of a specific user you should use firebase queries.
For example if you want to get all the tasks of a specific user, you should do:
let reference = Firebase(url: "https://{My-Firebase-Base-Reference}/tasks")
reference.queryOrderedByChild("creatorId").queryEqualTo(YOUR_CURRENT_USER_UID).observeEventType(.ChildChanged,
withBlock: { (snapshot: FDataSnapshot!) -> Void in
// Success
}) { (error: NSError!) in
// Error: Get Permissions Denied Here.
}
This way you could get all the events only related to your user, and protect the information by applying the security rules.
also if you want to allow only the creator to write their own tasks you should also consider the case where you create the task and write something like this:
"tasks": {
//You need to include the $task_id otherwise the rule will seek the creatorId child inside task and not inside your auto-generated task
"$task_id": {
".read": "auth.uid === data.child('creatorId').val()",
//this is to validate that if you are creating a task it must have your own uid in the creatorId field and that you can only edit tasks that are yours...
".write":"(newData.child('creatorId').val() === auth.uid && !data.exists()) || (data.child('creatorId').val() === auth.uid && data.exists())",
//you should add the index on to tell firebase this is a query index so your queries can be efficient even with larger amounts of children
".indexOn":"creatorId",
}
}
(check the syntax but that's the general idea)
A couple of comments:
If local persistence is on, and you have no internet connection and there is no local value, neither of the blocks will be called.
The observe code in the question is bugging me, it's not wrong but may be a bit clearer if it was like this:
reference.observeEventType(.ChildChanged, withBlock: { snapshot in
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})
Edit:
The question is clearer now
I'd like a user to only be able to read objects that they created.
I'll demonstrate via the following:
A Firebase structure:
posts
post_0
created_by: uid_0
msg: some message
post_1
created_by: uid_1
msg: another message
post_2
created_by: uid_2
msg: yippee
and the rules that will allow a user to only read from the post node they created
"rules": {
".read": false,
".write": false,
"posts": {
".read": false,
"$post_id": {
".read": "root.child('posts').child($post_id).child('created_by').val() == auth.uid",
".write": "auth != null"
}
}
}
then the code to test. We assume the current users uid is uid_0:
let reference = self.myRootRef.childByAppendingPath("posts/post_0")
reference.observeEventType(.ChildAdded, withBlock: { snapshot in
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})
The above code will allow a read from node post_0 which is the post uid_0 created as indicated by the created_by child.
If the path is changed to posts/posts_1 (or anything else) for example, the read is denied.
This answer my not directly answer the question as if you are expecting the rules to 'block' or 'filter' the resultant data, that isn't what rules are for (per the other answer).
So you may want to go down a different path by constructing a query to pull out just the posts you want to work with based on the created_by = auth.uid like this
let reference = self.myRootRef.childByAppendingPath("posts")
reference.queryOrderedByChild("created_by").queryEqualToValue("uid_1")
.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})

Resources