How to check a firebase subnode for a specific value (UID)? - ios

I'm trying to query Firebase to check if the current user's uid is listed in a room's "participants". If so, I grab the info for that room.
Below is my current observer which listens to ALL rooms in the app, not just the rooms the user is a participant of. But I need to perform a check first, to see which rooms the current user's UID is listed in; if there's a match (the uid is in a room's participants), THEN get the data for that room:
private func observeRooms() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {print("Error getting user UID"); return}
roomRefHandle = roomRef.observe(.childAdded, with: { (snapshot) -> Void in
let roomData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
guard let name = roomData["roomName"] as! String! else {print("Error getting user name"); return}
self.usersRooms.append(Room(id: id, name: name, participants: [uid]))
self.tableView.reloadData()
})
}
This is how the rooms are structured in the database:
"rooms" : {
"-Ki6TJWO-2R1L4SyhSqn" : {
"messages" : {
"-Ki6TWrXxWqjaRJAbyVt" : {
"senderId" : "tzfHgGKWLEPzPU9GvkO4XE1QKy53",
"senderName" : "Timothy",
"text" : "Room One message"
}
},
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true
},
"roomName" : "Room One"
},
"-Ki6TKOnmToeUuBzrnbb" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true
},
"roomName" : "Room Two"
},
"-Ki6TLGC1Encm1v-CbHB" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true
},
"roomName" : "Room Three"
}
}
How can I change my function so that it first checks all the room's participants for the current user's uid, before grabbing the values?
Thanks for any suggestions!
EDIT: Jay's approach:
private func observeRooms() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {print("Error getting user UID"); return}
let queryRef = roomRef.queryOrdered(byChild: "participants/\(uid)").queryEqual(toValue: true)
queryRef.observe(.childAdded, with: { snapshot in
let roomDict = snapshot.value as! [String: AnyObject]
let id = snapshot.key
let roomName = roomDict["roomName"] as! String
let participants = roomDict["participants"] as! [String: AnyObject]
let numberOfParticipants = participants.count
print("\nRoom Name: \(roomName)")
print("Participants: \(participants)")
print("Room ID: \(id)\n")
self.usersRooms.append(Room(id: id, name: roomName, participants: [uid]))
self.tableView.reloadData()
})
}

To keep it super simple, store a reference to the user in each room they belong to.
rooms
room_0
room_name: "My Room"
users
user_1: true
user_2: true
room_1
room_name: "Their Room"
users
user_1: true
Then a simple query which will gather all the needed data, and will also leave an observer attached so if this user joins any new rooms the app will be notified.
let roomsUsersRef = self.ref.child("rooms")
let queryRef = roomsUsersRef.queryOrdered(byChild: "users/user_1").queryEqual(toValue: true)
queryRef.observe(.childAdded, with: { snapshot in
let roomDict = snapshot.value as! [String: AnyObject]
let roomName = roomDict["room_name"] as! String
let usersDict = roomDict["users"] as! [String: AnyObject]
let userCount = usersDict.count
print("Room: \(roomName) has \(userCount) users")
})
and the output
Room: My Room has 2 users
Room: Their Room has 1 users
You could expand on this with .childChanged and .childRemoved to keep track of any events that occur in a room this user belongs to. So if another user joins or leaves a room this user is in, app will be notified; if the owner of the room boots this user from the room, the app will also be notified.

Ideally you would create a separate node in your app to store the information you want. Something like:
{
"usersRooms": {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53": {
"-Ki6TJWO-2R1L4SyhSqn": true,
"-Ki6TKOnmToeUuBzrnbb": true,
"-Ki6TLGC1Encm1v-CbHB": true
}
}
}
This would allow you to get the node for the user and instantly see what rooms they are apart of. Once you've done that you can loop over the results to get the individual rooms. In terms of keeping this list updated look into firebase functions or roll your own.

Related

How to grab Users personal list in Firebase to a tableview

I am trying to simply fetch a users favourite maps onto a tableview.
something that i thought would be very basic but turned out to be extremely difficult.
The code here is the best that i have managed so far, Attempting to somehow reference a (users id) with a (yourmaps id) to fetch specific information.
For example. Since the user has made 1 map his favourite(with id (-LpY4XEER-b21hwMi9sp)). I want to look through all maps within root["yourmap"] and only fetch his map onto a tableview.
Firebase
"users" {
"6g55cHXH4begwooHQvO4EKNV3xm1" : {
"photoURL" : "https://firebasestorage.googleap...",
"username" : "lbarri",
"yourmaps" : {
"-LpY4XEER-b21hwMi9sp" : true
}
}
}
"yourmaps": {
"-LpY4XEER-b21hwMi9sp" : {
"author" : {
"photoURL" : "https://firebasestorage.googleapis.com/v...",
"uid" : "6g55cHXH4begwooHQvO4EKNV3xm1",
"username" : "lbarri"
},
"mapmoderators" : {
"6g55cHXH4begwooHQvO4EKNV3xm1" : true
},
"mapphotoURL" : "https://firebasestorage.googleapis...",
"mapusername" : "Hello World"
},
"-LpYo_pQ8zIOGHHlNU1Q" : {
"author" : {
"photoURL" : "https://firebasestorage.googleapis.com/v...3",
"uid" : "RLFK9xnvhccTu2hbNHq0v05J2A13",
"username" : "lbarri"
},
"mapmoderators" : {
"RLFK9xnvhccTu2hbNHq0v05J2A13" : true
},
"mapphotoURL" : "https://firebasestorage.googleapis.com/...",
"mapusername" : "Dream"
}
}
Swift
func getCurrentUserMaps() {
guard let userProfile = UserService.currentUserProfile else { return }
let currentUserId = userProfile.uid
let userRef = Database.database().reference().child("users").child(currentUserId)
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
let root = snapshot.value as? NSDictionary
if let mapsByUser = root!["yourmaps"] as? [String: Bool] {
for (documentId, status) in mapsByUser {
if status {
// Document is true, check for the maps
self.fetchyourmaps(key: documentId, owner: currentUserId)
}
}
}
}) { (error) in
print(error.localizedDescription)
}
}
func fetchyourmaps(key:String, owner:String) {
let yourMapRef = Database.database().reference().child("yourmaps")
yourMapRef.observeSingleEvent(of: .value, with: {snapshot in
let user = snapshot.value as? NSDictionary
if let mapsByUser = user!["mapmoderators"] as? [String: Bool] {
for (userId, status) in mapsByUser {
if userId == owner && status == true {
print("Owner \(owner) manages this \(user)")
var tempYourMap = [YourMapProfile]()
for key in (snapshot.value as? NSDictionary)! {
let childSnapshot = key as? DataSnapshot
let dict = childSnapshot!.value as? [String:AnyObject]
let author = dict!["author"] as? [String:AnyObject]
let uid = author!["uid"] as? String
let username = author!["username"] as? String
let photoURL = author!["photoURL"] as? String
let url = URL(string:photoURL!)
let mapusername = dict!["mapusername"] as? String
let mapphotoURL = dict!["mapphotoURL"] as? String
let mapurl = URL(string:mapphotoURL!)
let userProfile = UserProfile(uid: uid!, username: username!, photoURL: url!, mapPoints: mapPoints!)
let yourmapprofile = YourMapProfile(mapid: childSnapshot!.key as! String, mapauthor: userProfile, mapusername: mapusername!, mapphotoURL: mapurl!)
tempYourMap.append(yourmapprofile)
}
self.yourmaps = tempYourMap
self.tableView.reloadData()
}
}
}
})
}
print("Owner \(owner) manages this \(user)") does print the correct maps onto the console
After that line it is when i cant figure out how to package the information to my tableview.
I have searched everywhere for information on how to retrieve data from Firebase when referencing one root folder to another but i cant find anything helpful. So any link/guide/ tutorial etc would be appreciated and i'll gladly take it from there. Is this at least how you are supposed to do it?
There are a few ways to do this but here's two: Option 1 is to leverage a deep query to get the maps that are this users favorites. The second is to iterate over the users maps and pull each one at a time.
Option 1:
Start with a maps node like this
allMaps
map_0
favorite_of
uid_0: true
uid_3: true
map_user_name: "Larry"
map_1
favorite_of
uid_2: true
map_user_name: "Moe"
map_2
favorite_of
uid_0: true
map_user_name: "Curly"
Then, a deep query to get all the favorite maps of uid_0
func queryToGetMyFavoriteMaps() {
let uid = "uid_0"
let ref = self.ref.child("allMaps")
let path = "favorite_of/" + uid
let query = ref.queryOrdered(byChild: path).queryEqual(toValue: true)
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
print(child) //prints the map_0 & map_2 nodes since that's the favorite onces
}
})
}
Option 2
Change up the allMaps node since we won't be doing a query
allMaps
map_0
map_user_name: "Larry"
map_1
map_user_name: "Moe"
map_2
map_user_name: "Curly"
and then the users node will be something like this
users
uid_0
name: "Frank"
favorite_maps:
map_0: true
map_2: true
uid_1
name: "Leroy"
favorite_maps:
map_1: true
and then the code that reads uid_0's favorite_maps node, and gets the keys from that snapshot, and then iterates over them, reading the map nodes one at a time.
func iterateToGetFavoriteMaps() {
let uid = "uid_0"
let userRef = self.ref.child("users").child(uid)
userRef.observeSingleEvent(of: .value, with: { snapshot in
if let mapRefs = snapshot.childSnapshot(forPath: "favorite_maps").value as? [String: Any] {
let mapKeys = mapRefs.keys //note mapKeys is a Dict so we can directly access the keys
for key in mapKeys {
let mapRef = self.ref.child("allMaps").child(key)
mapRef.observeSingleEvent(of: .value, with: { mapSnapshot in
let name = mapSnapshot.childSnapshot(forPath: "mapUserName").value as? String ?? "No Name"
print(name)
})
}
}
})
}

Remove value when listing firebase child node on tableViewCell

I am trying to list all the users from a firebase node in a table view (this is working) but would then like to remove the current user from the list (this is not working).
I've tried to use removeValue() but it still ran with the current user on the table view - also I don't want to remove the user from firebase
I then tried to make it run only if the the user autoId is not equal to the current users Id but it still shows up on the table view cell
Would appreciate any help :)
My firebase structure is like this:
"users" : {
"8x1SGi2P0KVOKm4i60JQLP1bdU63" : {
"fullname" : "bbbb",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2F8x1SGi2P0KVOKm4i60JQLP1bdU63?alt=media&token=7932b14f-c2d8-46fd-9dd1-c607217fe8d3",
},
"B7rwHiCTlphZjKXfPSEzkIwl8RH2" : {
"fullname" : "Aaaa ",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FB7rwHiCTlphZjKXfPSEzkIwl8RH2?alt=media&token=072e1f41-935e-430d-af99-dc640381f8e6",
},
"FRuuk20CHrhNlYIBmgN4TTz3Cxn1" : {
"fullname" : "eeee",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FFRuuk20CHrhNlYIBmgN4TTz3Cxn1?alt=media&token=bf89b903-a51a-4d6d-bdef-fe2667d78842",
},
Code to which lists users:
func observeUsers(completion: #escaping (UserModel) -> Void) {
REF_USERS.observeSingleEvent(of: .childAdded, with: { snapshot in
if let dict = snapshot.value as? [String: Any] {
let user = UserModel.transformUser(dict: dict, key: snapshot.key)
//line below used first to remove value from listing on table view
//Api.User.REF_USERS.child("users").child(Api.User.CURRENT_USER!.uid).removeValue()
//line below - if user autoID is not equal to the current user then list
if user.id != Api.User.CURRENT_USER?.uid {
completion(user)
}
}
})
}
EDIT:
func loadUsers() {
var allUsers = [String]()
Api.User.REF_USERS.observe(.value, with: { snapshot in
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
allUsers.append(key)
print(allUsers)
}
})
}
instead of removing try this
create model of your tabledata
then create an empty array of it
while fetching the data append all except that which has same currentuser.uid
then reload the tableview this will show all the data except the current user
this is the code as promised:
but this need a little modification in your database
make your database like this
"users":{
"childbyautid":{
"fullname": "name",
"profileimageurl": "your url",
"userid": "userid"
}
then you can write like this
var myArr = [String]()
Database.database().reference.child("users").observe(.value){(snapshot) in
if snapshot.childcount > 1 { self.myArr.removeAll()
for data in snapshot.children.allObjects as! [DataSnapshot]{
if let d = data.value as? [string: any]{
if Auth.auth.currentuser.uid != d["userid"]{
myArr.append(d["name"]}else{print("this is the user itself"}}}
You can control this in you loadUsers by adding userId check like below
func loadUsers() {
var allUsers = [String]()
Api.User.REF_USERS.observe(.value, with: { snapshot in
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
if key != currentUser.id{
allUsers.append(key)
}
print(allUsers)
}
})
}

Swift Firebase Save/Update multiple parents with the same child values

I have an array of strings which is the "uid's" of users. I am trying to append data/children to these multiple "uid's". Adding children or updating children to individual parents/users is easy and I understand how to do it. The problem is that this array can either contain 1 uid or 50 uid's. Is it possible for me to take these uid's and then update them with the same value? I am unsure what code to provide since I am just trying everything to attack this.
With the code below, this is me send a message to other users.
Array of uid strings
var data = [String]()
Sample code of me sending a message to 2 users, just wanted to provide something here to show I know how to update/save data
private func sendMessageWithProperties(_ properties: [String: Any]) {
let businessRef = Database.database().reference().child("Business Group Chats Messages").child((group?.uid)!).child((Auth.auth().currentUser?.uid)!)
let ref = Database.database().reference().child("Business Group Chats Messages").child((Auth.auth().currentUser?.uid)!).child((group?.businessName)!)
let businesChildRef = businessRef.childByAutoId()
let childRef = ref.childByAutoId()
let fromID = Auth.auth().currentUser!.uid
let timeStamp = Int(Date().timeIntervalSince1970)
var value:[String: Any] = ["fromId" : fromID, "timeStamp" : timeStamp, "name": self.loggedInUserData?["name"] as? String]
properties.forEach { (k,v) in
value[k] = v
}
childRef.updateChildValues(value) { (err, ref) in
if err != nil {
print(err!)
return
}
Database.database().reference().child("Business Group Chats").child((self.group?.uid)!).child((Auth.auth().currentUser?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp, "businessName":(self.group?.businessName)!])
Database.database().reference().child("Business Group Chats").child((Auth.auth().currentUser?.uid)!).child((self.group?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp])
self.inputContainerView.inputTextField.text = nil
}
}
Here is me taking that array of "uid's" and then pulling and printing that I can access each "uid" through a array of strings. Allowing me to access, now I can append data to each.
Database.database().reference().child("Businesses").observe(.value, with: { snapshot in
if snapshot.exists() {
self.businessUID = snapshot.value as? NSDictionary
if let dict = snapshot.value as? NSDictionary {
for item in dict {
let json = JSON(item.value)
let businessUid = json["uid"].stringValue
for uid in self.data {
if uid == businessUid {
//print(uid)
self.businessessuids = uid
print(self.businessessuids)
Database.database().reference().child("Businesses").child(self.businessessuids).observe(.value, with: { snapshot in
print(snapshot)
print("Trying to pull data from multiple strings right here this shoudld work")
})
print("printing the values to match with the string UID's")
}
}
}
}
} else {
print("does not exist")
}
})

Unable to get the Firebase to invoke the observe/observeSingleEvent callback

I want to get a user corresponding to an event
I have a list of Events
let eventsRef = FIRDatabase.database().reference.child("Events")
I have a list of Users
let usersRef = FIRDatabase.database().reference.child("Users")
My Event and User model is as below
Events
Event1
eventHost: user1_uid
Event2
eventHost: user2_uid
Users
User1
email: email1
User2
email: email2
The following callback (in the Event model) is never invoked:
if let userKey = eventData["eventHost"] as? String {
userRef = usersRef.child(userKey)
userRef.observeSingleEvent(of: .value, with: { snapshot in
...
})
}
I can confirm that I have not enabled disk persistence and that user uid is available. Is there anything I am doing obviously wrong?
======
EDIT: The simplified event model
import Foundation
import Firebase
class Event {
// event metadata
private var _eventId: String!
private var _eventHost: User!
var eventId: String {
return _eventId
}
var eventHost: User {
return _eventHost
}
init(eventId: String, eventData: Dictionary<String, Any>) {
self._eventId = eventId
if let userKey = eventData["eventHost"] as? String {
let usersRef = FIRDatabase.database().reference().child("Users")
let userRef = usersRef.child(userKey)
print(userRef)
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
print("USERY: \(snapshot.key)")
if let userDict = snapshot.value! as? Dictionary<String, Any> {
let id = snapshot.key
self._eventHost = User(userId: id, userData: userDict)
}
})
}
}
}
The print(userRef) resolves to
https://xxxx.firebaseio.com/Users/AFHpS3npOga4tfj10GS2HGeT9uJ3`
which is a valid object in my Firebase structure. Snippet of Firebase User structure
"AFHpS3npOga4tfj10GS2HGeT9uJ3" : {
"email" : "test#gmail.com",
"firstName" : "Wilma",
"lastName" : "Flintstone",
"profileImageUrl" : "http://images.iimg.in/c/569f4771c45d324bda8b4660-4-501-0-1453279096/google/user-icon-png-pnglogocom.img",
"provider" : "Firebase",
"userId" : "AFHpS3npOga4tfj10GS2HGeT9uJ3"
},
can you take a look at the rules of your firebase database :
It should be something like this
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
in order to let non authenticated people read / write data you should do the following :
{
"rules": {
".read": true,
".write": true
}
}
From the code you post I don't see any problem ...
If this doesn't work please post more code and tell us what you plan to do exactly so we can better help you.
Edit: To go along with the above suggestion, adding a cancel block to the observe function will reveal if there's a rule issue. If the user cannot access the node, the Xcode console will print 'Permission Denied'
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
print("USERY: \(snapshot.key)")
if let userDict = snapshot.value! as? Dictionary<String, Any> {
let id = snapshot.key
self._eventHost = User(userId: id, userData: userDict)
}
}, withCancel: { error in
print(error.localizedDescription)
})

Firebase observer reading data twice

My observer listens for new rooms being added in Firebase then updates a local array when one is added. It's working except when I add a room to Firebase, two of that room are added to the array. For example if I have 4 rooms in Firebase (Room One, Room Two, etc), when the app loads, there are 8 rooms in the table view (two Room One's, two Room Two's, etc). Then adding, for example, Room Five, two Room Fives would show up.
private func observeRooms() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {print("Error getting user UID"); return}
let userRoomRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").child(uid).child("rooms")
userRoomRef.observe(.childAdded, with: { (roomSnap) in
for eachRoom in roomSnap.value as! [String : AnyObject] {
let roomID = eachRoom.key as! String
print("Roomsnap 2 (observer): \(roomSnap.value)")
if let roomInfo = roomSnap.value as? [String : AnyObject] {
guard let roomName = roomInfo["roomName"] as! String! else {print("Error getting room name"); return}
guard let participants = roomInfo["participants"] as! [String]! else {print("Error getting room participants"); return}
print("\n\nRoom Name: \(roomName)\nRoom Participants: \(participants)\nRoom ID: \(roomID)\n")
self.usersRooms.append(Room(id: roomID, name: roomName, participants: participants))
print("User's Rooms: \(self.usersRooms)\n")
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
})
}
Why is the data being fetched twice? Is there a way to write the function to just read each room once?
Firebase DB JSON:
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"gender" : "male",
"handle" : "TestHandleOne",
"name" : "Timothy",
"profilePicture" : "https://graph.facebook.com/*removed*/picture?type=large&return_ssl_resources=1",
"rooms" : {
"-KhY2GnJOxBwdPK669ui" : {
"participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
"roomName" : "Room One"
},
"-KhY2Hnz48lTtRpzmBuw" : {
"participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
"roomName" : "Room Two"
},
"-KhY2IZL4l16dMxGopt6" : {
"participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
"roomName" : "Room Three"
},
"-KhY8SdHnkfI7bZIpyjI" : {
"participants" : [ "tzfHgGKWLEPzPU9GvkO4XE1QKy53" ],
"roomName" : "Room Four"
}
}
}
roomSnap.value printed to the console, each room printed twice with different values for Room ID. This is Room One for example:
Working function (final edit):
private func observeRooms() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {print("Error getting user UID"); return}
let userRoomRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").child(uid).child("rooms")
roomRefHandle = userRoomRef.observe(.childAdded, with: { (snapshot) -> Void in
let roomData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
guard let name = roomData["roomName"] as! String! else {print("Error getting user name"); return}
self.usersRooms.append(Room(id: id, name: name, participants: [uid]))
// Add new room to "rooms" in Firebase
let roomDict = ["roomName" : name, "participants": [uid]] as [String : Any]
let newRoomRef = self.roomRef.child(id)
newRoomRef.setValue(roomDict)
self.tableView.reloadData()
})
}
The .childAdded function reads in each child, one at a time, when first called and then any new children thereafter.
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.
The key here is that it automatically iterates over each room in the node one at a time. The code in the question is redundant which leads to multiple entries in the array.
Here's a super short way to populate the array with each child node
let usersRef = self.ref.child("users")
let uid = "user_0" //some uid
let userRoomRef = usersRef.child(uid).child("rooms")
userRoomRef.observe(.childAdded, with: { roomSnap in
let roomDict = roomSnap.value as! [String: AnyObject]
let roomID = roomSnap.key
let roomName = roomDict["roomName"] as! String
print("roomID = \(roomID) roomName = \(roomName)")
var aRoom = Room() //could also initWith...
aRoom.id = key
aRoom.name = roomName
//aRoom.participants = partipants
self.usersRooms.append(aRoom)
self.tableView.reloadData()
})
Edit
In response to a comment, an alternative structure is
users
uid_0
name: "Tom"
uid_1
name: "Jerry"
rooms
room_0
name: "Cool Room"
users:
uid_0: true
uid_1: true
room_1
name: "Romper Room"
users
uid_0: true
Tom and Jerry both belong to the Cool Room, but only Tom belongs to Romper Room.
If you want to populate an array/tableView with the rooms that Tom belongs do, you can simply deep query on the rooms/users/ where uid_0 == true.

Resources