How to automatically update information changed in Firestore - swift

I have this code to display username that was stored in Firestore when signing up:
func displayUserName(){
let db = Firestore.firestore()
if let uid = user?.uid{
db.collection("PROFILE").document(uid).getDocument { (snap, err) in
guard let data = snap else {return}
let firstname = data.get("firstName") as! String
self.firstName = firstname
}
}
}
but, when I change the name in Firestore, I need to relaunch the app so it can update. is it possible to update this name without needing to relaunch the app?

Based on the comment given, I just changed getDocuments for addSnapshotListener
db.collection("PROFILE").document(uid).addSnapshotListener { (documentSnapshot, error) in
guard let datas = documentSnapshot else {return}
let firstname = datas.get("firstName") as! String
self.firstName = firstname
}

Related

SwiftUI - Firebase: Value of type 'String' has no member 'documentID'

When I try to get user data from firebase I have an error. The error message is:
Value of type 'String' has no member 'documentID'
The line with the error is the line fetchUser(uid: uid.documentID) { (user) in:
let title = doc.document.data()["title"] as? String ?? "No Title"
let time = doc.document.data()["time"] as? Timestamp ?? Timestamp(date: Date())
let pic = doc.document.data()["url"] as? String ?? "No URL"
let uid = doc.document.data()["uid"] as? String ?? ""
// getting user Data...
fetchUser(uid: uid.documentID) { (user) in
And this is my FetchUser model:
// Global Refernce
let ref = Firestore.firestore()
func fetchUser(uid: String,completion: #escaping (UserModel) -> ()){
ref.collection("Users").document(uid).getDocument { (doc, err) in
guard let user = doc else{return}
let username = user.data()?["username"] as? String ?? "No Username"
let pic = user.data()?["imageurl"] as? String ?? "No image URL"
let bio = user.data()?["bio"] as? String ?? "No bio"
let uid = user.data()?["uid"] as? String ?? ""
DispatchQueue.main.async {
completion(UserModel(username: username, pic: pic, bio: bio, uid: uid))
}
}
}
Below is code to that checks the users uid. On the line that starts "ref.collection..." the error "Cannot find 'uid' in scope" is thrown.
func checkUser(){
let ref = Firestore.firestore()
if let currentUser = Auth.auth().currentUser {
let uid = currentUser.uid
} else {
print("No Authenticated User")
return
}
ref.collection("Users").whereField("uid", isEqualTo: uid).getDocuments { (snap, err) in
if err != nil{
// No Documents..
// No User Found...
self.registerUser.toggle()
self.isLoading = false
return
}
if snap!.documents.isEmpty{
self.registerUser.toggle()
self.isLoading = false
return
}
self.log_Status = true
}
}
Let me take the first part of your code to show where the issue is. Note how much easier it is to read when properly formatted
func checkUser() {
let ref = Firestore.firestore()
if let currentUser = Auth.auth().currentUser {
//NOTE! Note that this area is encapsulated with brackets { and }
//That means it's is own 'space' and anything defined in this area
//only exists in this area
let uid = currentUser.uid //<- uid only exists here
} else {
print("No Authenticated User")
return
}
//uid no longer exists and cannot be referenced
// e.g. it's not in 'scope' at this point
ref.collection("Users").whereField("uid", isEqualTo: uid)
However, if you look at where the let ref = Firestore line is located, it's at the top level within the checkUser function and will exist throughout the function.
There are many way to do this; here's one using a guard statement
func checkUser() {
let ref = Firestore.firestore()
guard let currentUser = Auth.auth().currentUser else {
print("no user!")
return
}
// currentUser flows through to here because it was created with
// a guard statement, so now we know it's populated and can
// get the uid property value from it
let uid = currentUser.uid
ref.collection("Users").whereField("uid", isEqualTo: uid)
guard is pretty neat in that it not only allows you to instantiate a var while a the same time as protecting your code from a nil situation, it also allows the var to flow through to the code following the guard.
I'm writting this answer as a community wiki, since the issue was resolved from the comments section, in order to provide a proper response to the issue reported.
The error came while trying to get the uid as fetchUser(uid: uid.documentID), instead the correct way is by doing fetchUser(uid: uid)
Then an error mentioning Document path cannot be empty appeared, which was mainly due to the fact that no entries with were loaded, the best way to avoid this is to load documents on the consulted path without nil values

set the value for a variable once or initialize a variable - Swift

Im using Firebase Firestore as my database, and I want to limit the number of calls to the database so my plan is to initialize an object once and have it ready for me to use anywhere throughout my app. What I want to do is basically give the publicUsername a value collected from the database once, so once publicUsername has a value, I don't want to call to the database anymore. I then want to call publicUsername through my app such as when I want to show the user their username I can just call the publicUsername value as so: usernameLabel.text = publicUsername.
Here are 2 methods I've tried using, but no luck!:
let publicUsername = Uzer().uzername
class Uzer {
var uzername = ""
init() {
let uid = Auth.auth().currentUser?.uid
let database = Firestore.firestore().collection("Users").document(uid!)
database.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let mydata = docSnapshot.data()
let username = mydata!["Username"] as? String ?? ""
self.uzername = username
}
}
}
OR
let publicUsername = User().username
class Users {
var username = ""
func returnUsername () -> String {
if username == "" {
let uid = Auth.auth().currentUser?.uid
let database = Firestore.firestore().collection("Users").document(uid!)
database.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let mydata = docSnapshot.data()
let username = mydata!["Username"] as? String ?? ""
self.username = username
}
return username
} else {
return username
}
}
}

Get Dictionary of Users Uid on Firebase

This is my case; I need a dictionary of all of the users in my app to use didSelectRowAt on a UITableView to get the searched users uid.
my firebase database is designed like this:
not allowed to upload Images so link here
Also, I succesfully made a string/dictionary of the users names by doing this;
let rootRef = Database.database().reference()
let query = rootRef.child("users").queryOrdered(byChild: "users")
query.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let value = child.value as? NSDictionary {
// declaring a user in class User() which was made with strings(name, email, ect.)
let user = User()
let name = value["name"] as? String ?? "name not found"
// making the string of it go into the user.name
user.name = name
// appending it into a variable of class user()
self.users.append(user)
}
}
}
I'm not sure how to do this because the uid's are all different and don't have a name to it (ex. email: test#test.com).
Perhaps I need to restructure my database to allow this but I'm not too sure. Thanks for the help!
One way is to keep a field for uid along with the name, email and profileImageURI fields.
let rootRef = Database.database().reference()
guard let uid = rootRef.child("users").childByAutoId().key
Now, send the uid as a field while saving the data to firebase.
While retrieving the data you can map the data as follows :
let query = rootRef.child("users").queryOrdered(byChild: "users")
query.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let value = child.value as? NSDictionary {
// declaring a user in class User() which was made with strings(name, email, etc.)
var dict = [String:String]()
let user = User()
let name = value["name"] as? String ?? "name not found"
// making the string of it go into the user.name
user.name = name
user.uid = value["uid"]
dict[name] = "\(uid)"
}
}

Firebase don't send me my value into my variable

I've got a code which normally should return to me a value from Firebase.
My Firebase struct is :
Experience{
UserId{
LDG_DAY: "4"
LDG_NIGHT: "0"
APCH_IFR: "0"
}
}
My code is :
func getUserExp(){
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) {(snapshot) in
if snapshot.hasChild(userID!){
let value = snapshot.value as? NSDictionary
let ldg_day = value?["LDG_DAY"] as? String ?? "123"
let ldg_night = value?["LDG_NIGHT"] as? String ?? "0"
let apch_ifr = value?["APCH_IFR"] as? String ?? "0"
self.intLdgDay = Int(ldg_day)!
self.intLdgNight = Int(ldg_night)!
self.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
}
}
}
Now the code didn't work as I would like... In fact my code return the basic as? String ?? "123" value but the snapshot.value get the good value from firebase ...
What's wrong ? I use this code for many other part of my app and no problems about it ?
Thanks for your help
I believe you want to ensure the node exists before trying to read the child data.
NOTE:
I see the path to read has the uid commented out so it's unclear if you intended to read a single user (leaving in the uid) or if you actually wanted to load every user at one time (thousands). This answer assumes you are intending to read that specific user node only. See #Callam answer if you intended to read ALL of the users nodes at one time.
The code you have now is using snapshot.hasChild which looks within the node to see if the child, the users uid exists, and it doesn't so the code will always fail.
if snapshot.hasChild(userID!)
I think what you want to do is use snapshot.exists to ensure it's a valid node before reading. Here's the code:
let experienceRef = self.ref.child("Experience")
let usersExpRef = experienceRef.child(uid)
usersExpRef.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as! [String: Any]
let ldg_day = value["LDG_DAY"] as? String ?? "123"
print("string = \(ldg_day)")
} else {
print("the \(uid) node does not exist")
}
}
I would also suggest safely unwrapping options before attempting to work with them as they could be nil, and that would crash your code.
guard let thisUser = Auth.auth().currentUser else { return }
let uid = thisUser.uid
Note I also replaced the old objc NSDictionary with it's Swifty counterpart [String: Any]
Assuming your struct is from the root, and Experience contains more than one user ID, your code is currently observing the value for all user IDs since the /*.child(userID!)*/ is commented out.
Therefore you are requesting every user's experience and checking on the client if the current user exists as a child – this will succeed if the current user's ID is present at Experience/$uid.
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.hasChild(userID!) {
let value = snapshot.value as? NSDictionary
Now we have a snapshot with all Experiences and we've confirmed that it has a child for the current user's ID – we would need to get that child and cast the value of that to a dictionary.
let value = snapshot.childSnapshot(forPath: userID).value as? NSDictionary
This fixes the issue but obviously, we don't want to download every experience on a single user's device, and they maybe shouldn't even have the permission to request that reference location either.
So if you uncomment .child(userID!), the snapshot will be of just one Experience, so snapshot.hasChild(userID!) will fail. Instead, you can use snapshot.exists() and/or a conditional cast to determine if the snapshot for the userID is existent and/or thereby castable.
func getUserExp() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as? [String:String]
let ldg_day = value?["LDG_DAY"] ?? "123"
let ldg_night = value?["LDG_NIGHT"] ?? "0"
let apch_ifr = value?["APCH_IFR"] ?? "0"
self?.intLdgDay = Int(ldg_day)!
self?.intLdgNight = Int(ldg_night)!
self?.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \(snapshot.key) doesn't exist")
}
}
}
You can clean this up a bit with a struct and extension.
// Experience.swift
struct Experience {
var ldg_day: String
var ldg_night: String
var apch_ifr: String
}
extension Experience {
static var currentUserRef: DatabaseReference? {
return Auth.auth().currentUser.flatMap {
return Database.database().reference(withPath: "Experience/\($0.uid)")
}
}
init?(snapshot: DataSnapshot) {
guard snapshot.exists() else { return nil }
let value = snapshot.value as? [String:String]
self.ldg_day = value?["LDG_DAY"] ?? "123"
self.ldg_night = value?["LDG_NIGHT"] ?? "0"
self.apch_ifr = value?["APCH_IFR"] ?? "0"
}
}
Et voilà,
func getUserExp() {
Experience.currentUserRef?.observeSingleEvent(of: .value, with: { [weak self] in
if let experience = Experience(snapshot: $0) {
self?.intLdgDay = Int(experience.ldg_day)!
self?.intLdgNight = Int(experience.ldg_night)!
self?.intApchIfr = Int(experience.apch_ifr)!
print("string = \(experience.ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \($0.key) doesn't exist")
}
})
}

Firebase: Very Slow data show on Label text

I'm trying to observe information from my firebase database and store it in a dictionary, the problem is when I try to show these data on a label it takes a lot of time about 30 seconds, can you solve it please.
#objc func fetchUser() {
let user = Auth.auth().currentUser
if let user = user {
let uid = user.uid
let email = user.email
ref?.child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String : AnyObject] {
print(dictionary)
self.disName.text! = dictionary["DisplayName"] as! String
}
})
disUID.text = uid
disEmail.text = email
}
}