This question already has an answer here:
Access Firebase variable outside Closure
(1 answer)
Closed 3 years ago.
I have a function bellow called retrieveUsers() which is called inside a function which fetches all posts under a Post node.
Inside the code found bellow I save the current UID to a variable with global scope. But every time this function gets called the variable (lastUID) seems to be reset. Even though this method is being called in a for-loop essentially.
func retrieveUsers(value: Post) {
print(lastUID, "-=-=--=-=")
print(value.user.userID != lastUID, ": ", value.user.userID!, " email left old ... one -> ", lastUID)
if value.user.userID != lastUID {//Ignore this line
let ref = Database.database().reference()
if value.user.userID != nil {
let UID = value.user.userID!
print(UID, "Thoi s is the uid in the users fetch fx")
ref.child("users2").child(UID).observe(.value, with: { (snapshot) in
let email = "\(snapshot.childSnapshot(forPath: "email").value as! String)"
do {
value.user.email = email
//even with the two values being in here the same thing happens
}
self.lastUID = UID
self.lastEmail = email
})
}
} else {
print("ESC this is the same user")
value.user.email = lastEmail
}
}
What is going wrong?
Even though this method is being called in a for-loop essentially.
The global var is changed every cycle of the for - loop and since the calls to firebase are asynchronous , hence you can't catch every fetch with it's corressponding UID
You need to create a model and make those calls inside it with a property as the uid
Related
I am trying to loop through the participants of a conversation, and access the attributes previously assigned to said participants when they initially joined the conversation. The problem is, when I do access as shown below, the attribute() is empty.
I have verified that said participants on the Twilio Console do indeed have attributes assigned, and they are assigned as a String Type.
Am I missing something?
I am using swift, "TwilioConversationsClient" on an iOS client.
Any Help would greatly be appreciated.
func filterParticipants() {
guard let globalConvo = globalConversation else { return }
let participants = globalConvo.participants()
for participant in participants {
let participantAttributes = participant.attributes() // Comes up Empty
let attributeDict = participantAttributes.dictionary // Comes up Empty
let attributeString = participantAttributes?.string // Comes up Empty
}
}
}
This question already has answers here:
Storing asynchronous Cloud Firestore query results in Swift
(1 answer)
How do I save data from cloud firestore to a variable in swift?
(1 answer)
How can I change the order of functions triggered?
(1 answer)
Closed 1 year ago.
I am having trouble figuring out how to modify my code to take into account the fact that Firebase runs asynchronously.
func checkIfFriends(_ SearchUserUID: String) -> Bool {
var friendArray: [String]?
currentUserDetails.getDocument { (document, error) in
if let document = document, document.exists {
friendArray = document.data()!["Friends"] as? [String]
}
}
if let friends = friendArray {
return friends.contains(SearchUserUID)
}
return false
}
currentUserDetails is the document of the current user that stores an array of friends that the current user has under the String, "Friends", where each element of the array is the UID of the friend. I would like to check if two users are friends by first retrieving the array of friends of the current user, and then checking if that array contains the UID of the friend we are searching for.
The issue is that
currentUserDetails.getDocument { (document, error) in
if let document = document, document.exists {
friendArray = document.data()!["Friends"] as? [String]
}
}
runs asynchronously so my friendArray is considered to be nil and my method always returns false. I am very confused as to how I can modify such methods to return values and take into account the asynchronous nature of Firebase data retrieval. Could someone help me out? Thank you very much!
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
My structure in firebase is as follows:
app name
user ID
wins = 7
losses = 8
and my code to read the wins child node
ref = Database.database().reference().child(passUserID)
ref?.child("wins").observe(.childAdded, with: { (snapshot) in
//Convert the info of the data into a string variable
let getData = snapshot.value as? String
print(getData)
})
But it prints nothing.
To read data from Firebase you attach a listener to a path which is what creates a FIRDatabase reference. A FIRDatabaseReference represents a particular location in your Firebase Database where there is a key-value pair list of children. So in your case, you have created a Firebase reference to the key "wins" which only points to a value and not a key-value pair. Your reference was valid up to this point:
ref = Database.database().reference().child(passUserID)
//did you mean FIRDatabase and not Database??
This FIRDatabaseReference points to the key passUserID which has a key-value pair list of children ["wins":"7"] and ["losses":"8"] (NOTE: a key is always a string). So from your FIRDatabase reference, you create your observer as follows and read the value of "wins":
ref?.observe(.childAdded, with: { (snapshot) in
//Convert the info of the data into a string variable
if let getData = snapshot.value as? [String:Any] {
print(getData)
let wins = getData["wins"] as? String
print("\(wins)")
}
})
The Child added event will fire off once per existing piece of data, the snapshot value will be an individual record rather than the entire list like you would get with the value event. As more items come in, this event will fire off with each item. So if "losses" is the first record you might not get the value of "wins". Is this what you are trying to achieve? If what you really wanted to know is the value of "wins" at that particular location and to know if this value has ever changed you should use the .value observer as follows:
ref?.observe(.value, with: { (snapshot) in
//Convert the info of the data into a string variable
if let getData = snapshot.value as? [String:Any] {
let wins = getData["wins"] as? String
print("\(wins)") //check the value of wins is correct
}
})
Or if you just wanted to get the know the value of wins just once and you are not worried about knowing if there any changes to it, use the "observeSingleEvent" instead of "observe".
EDIT
I saw your image and now realize you might also have a problem with your reference. Your ref should actually be something like:
ref = FIRDatabase.database().reference().child("game-").child(passUserID)
You have obscured what "game" is but a valid reference to "wins" will include it.
SECOND EDIT
I will add the following so you can properly debug the problem. Use this pattern to observe the value and see if you get an error returned and what is says:
ref.observe(.value, with: { (snapshot) in
print(snapshot)
}, withCancel: { (error) in
print(error.localizedDescription)
})
Normally it will give you an error if you cannot access that Firebase location because of a database rule. It will also be a good idea to see if print(snapshot) returns anything as above.
You need this:
ref.child("YOUR_TOP_MOST_KEY").observe(.childAdded, with: { (snapshot) in
let keySnapshot = snapshot.key
//print(keySnapshot)
self.ref.child(keySnapshot).observe(.value, with: { (snapshot2) in
//print(snapshot2)
}) { (error) in
print("error###\(error)")
}
})
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.