Turning a firebase database value into a variable Swift - swift

I have a var declared and I can retrieve the value from Firebase Database but when I then print the var in ViewDidLoad, it is empty, I don't understand what's wrong. Thanks everyone
This is the answer I get when I print the var : this is the language
//
var language: String = ""
//
func getUserLanguage(completion:((String) -> Void)?) {
let ref = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
ref.child("users").child(uid).observe(.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let languageResult = value?["language"] as? String ?? ""
completion?(languageResult)
}
)}
//
getUserLanguage { (languageResult) in
self.language = languageResult
}
// I print the value in ViewDidload
print("this is the language\(self.language)")

Try to print languageResult in function - maybe you don't get it inside the function and the variable is not assigned
Make language variable public

I think you are missing the asynchronous nature of your code here. The getUserLanguage function will call the completion only when it gets callback from observe(.value, with: { (snapshot) method of firebase. It is asynchronous. You won't get the value of language right after you call getUserLanguage in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
getUserLanguage { (language) in
print(language) // --> prints the expected value
self.language = language
}
print(language) // --> prints ""
}
func getUserLanguage(completion: #escaping (String) -> Void) {
let ref = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
ref.child("users").child(uid).observe(.value, with: { (snapshot) in
let value = snapshot.value as? [String: Any]
let languageResult = value?["language"] as? String ?? ""
print("language: ", languageResult) // --> prints the expected value
completion(languageResult)
})
}

Related

Swift: How to return a dictionary in swift

i want to return a dictionary but not could not get it work. I am calling a firebase fetch function
func fetch(childpath:String) -> NSDictionary {
let firebaseUID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
let path = childpath.replacingOccurrences(of: "uid", with: firebaseUID!)
var result = [:] as NSDictionary
var children = [String]()
var childrenPath = path.split(separator: "/")
let childref = ref.child("\(childrenPath[0])").child("\(childrenPath[1])")
childref.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
result = value!
}) { (error) in
print(error.localizedDescription)
}
return result
}
I am calling the function above with this
var FirebaseVal = FirebaseManager().fetch(childpath: "customers/uid")
I want FirebaseVal to be the dictionary returned in fetch() but all I am getting is empty. The value is displayed inside the function but not at FirebaseVal.
How can I get it to return a dictionary?
The result inside your fetch(childpath:) method is set asynchronously, you cannot use return in this case - you'll always return nil because the value is not set "on the fly".
What you should do is pass a closure as the second parameter to this method and call it when you wish to notify of the method's completion. Try this:
func fetch(childpath:String, completion: #escaping (NSDictionary?) -> Void) {
let firebaseUID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
let path = childpath.replacingOccurrences(of: "uid", with: firebaseUID!)
var children = [String]()
var childrenPath = path.split(separator: "/")
let childref = ref.child("\(childrenPath[0])").child("\(childrenPath[1])")
childref.observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
completion(value)
}) { (error) in
print(error.localizedDescription)
completion(nil)
}
}
And now you use the method like this:
FirebaseManager().fetch(childpath: "customers/uid") { result in
if let result = result {
// use your dictionary
}
}

Firebase one of two observers not working

I have two observers, the second observer is dependent on the first observers value. I can't seem to get the first observer to work, I am not getting any errors on Xcode. The first function has to check the Users profile for information and then use that information to search for different information in the database. Here is my code:
func loadposts() {
ref = Database.database().reference()
let trace = Performance.startTrace(name: "test trace")
trace?.incrementCounter(named:"retry")
let userID = Auth.auth().currentUser?.uid
print(userID!)
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let one1 = value?["Coupon Book"] as? String ?? ""
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
}) { (error) in
print(error.localizedDescription)
}
delay(0.1) {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
Database.database().reference().child("Coupons").child(self.bogus.string(forKey: "bogus")!).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] as! String
let offerx = dict["offer count"] as! String
let logocomp = dict["logo"] as! String
let actchild = dict["childx"] as! String
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
self.tableview.reloadData()
print(self.posts)
}
}
}
trace?.stop()
}
Any help is appreciated.
self.bogus.string(forKey: "bogus"))" is nil because observeSingleEvent is an async method, so to get the required results you need to call the second observer inside the first observer or you can use the completion handler
You can use the completionHandler like this:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
func firstObserverMethod(completionCallback: #escaping () -> Void) {
ref.child("Users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let value = snapshot.value as? [String: Any] {
let one1 = value["Coupon Book"] as? String
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
completionCallback()
}
}) { (error) in
print(error.localizedDescription)
}
}
Now using the above method:
firstObserverMethod {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
guard let bogusString = self.bogus.string(forKey: "bogus") else {
print("bogus is not set properly")
return
}
Database.database().reference().child("Coupons").child(bogusString).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] ?? ""
let offerx = dict["offer count"] ?? ""
let logocomp = dict["logo"] ?? ""
let actchild = dict["childx"] ?? ""
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
DispatchQueue.main.async {
self.tableview.reloadData()
}
print(self.posts)
}
}
}
Note: You should use optional binding to get the values from optional
Since you are using the result of the 1st observer in the reference of your 2nd observer, it's a very bad idea to add the 2nd observer right below the first observer. And adding a delay won't be a viable solution : these two calls are asynchronous, which means that the reason why you are not getting might very likely be because the 2nd observer is triggered even before the 1st has returned any data.
The solution here, would be using a completion handler, or you could just incorporate your 2nd observer inside the completion block of the 1st, to be make sure that the proper order (1st observer -> 2nd observer) will always be respected.
It would look somehow like this:
func loadposts() {
// ...
// 1st Observer here
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get your value here
guard let one1 = snapshot.childSnapshot(forPath: "Coupon Book").value as? String else { return }
// 2nd Observer here. Now you can use one1 safely:
Database.database().reference().child("Coupons").child(one1).observe(.childAdded) { (Snapshot : DataSnapshot) in
// ...
}
})
}
Now, a couple of things that you could also improve in your code, while not directly related to the question:
I would suggest you to make use of guard statements instead force-unwrapping, which may end up in crashing your app at some point.
For example, you could check whether your current user exist or not like so:
guard let currentUserID = Auth.auth().currentUser?.uid else {
return
}
// Now you can use safely currentUserID
Also, when you try to get the data out of the snapshot, it's not a good idea either, to use force-casting. You would better write it in this way:
yourRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let text = child.childSnapshot(forPath: "text").value as? String, let somethingElse = child.childSnapshot(forPath: "otherValue").value as? NSNumber else {
return
}
// And so on, depending of course on what you have in your database.
}

Global variable appears to lose value (swift)

This is my code to extract data from firebase.
listofnames(double dimensional) and names(single dimensional) are global arrays of type String.
However, when I print listofnames an empty array is displayed in the output window whereas, if I comment the first line (self.listofnames = [[]]) the code functions properly.
I have linked ref with Firebase database
Please help.
var databasehandle:FIRDatabaseHandle?
let time = ["8AM - 9AM", "9AM - 10AM","10AM - 11AM", "11AM -12PM", "12PM - 1PM","1PM - 1:30PM","2PM - 3PM","3PM - 4PM","4PM - 5PM","5PM - 6PM","6PM - 7PM","7PM - 7:30PM"]
var ref:FIRDatabaseReference!
var day: String = "Monday"
var users :String = "First years"
func loaddata()
{
self.listofnames = [[]]
for x in 0...((time.count)-1)
{
ref.child(users).child(day).child(time[x]).observeSingleEvent(of: .value, with: {(snapshot) -> Void in
if let namesfromFirebase = snapshot.value as? [String:Any]
{
let y = namesfromFirebase.count
for i in 1...y
{
self.names.append(namesfromFirebase["s"+"\(i)"] as! String)
}
}
self.listofnames.append(self.names)
self.names = []
})
}
print(self.listofnames)
self.tableView.reloadData()
}
observeSingleEvent works asynchronously, the closure is called later.
There is an easy solution: Move the two lines into the closure:
func loaddata()
{
self.listofnames = [[]]
for x in 0..<time.count // no parentheses and math needed when using `..<`
{
ref.child(users).child(day).child(time[x]).observeSingleEvent(of: .value, with: {(snapshot) -> Void in
if let namesfromFirebase = snapshot.value as? [String:Any]
{
let y = namesfromFirebase.count
for i in 1...y
{
self.names.append(namesfromFirebase["s"+"\(i)"] as! String)
}
}
self.listofnames.append(self.names)
self.names = []
print(self.listofnames)
self.tableView.reloadData()
})
}
}

Swift service class to retrieve Firebase data

I have a service class to fetch data from my Firebase:
class Service {
var myName = String?
var myDev: String?
func getData() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Data").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
self.myName = name
let dev = value?["dev"] as? String ?? ""
self.myDev = dev
}) { (error) in
print(error.localizedDescription)
}
}
}
and realization in my Main class:
var service = Service()
override func viewDidLoad(){
super.viewDidLoad()
service.getData()
configureLabel()
}
private func configureLabel(){
self.titleLabel.text = service.myName
self.devLabel.text = service.myDev
}
The problem is: data from Firebase fetched only after my label got values of myName and myDev.Thus, this values is nil.
this is not the best solution, but it should solve your problem.
struct MyStructure {
var name = ""
var dev = ""
init(with dictionary: [String: String]) {
if let name = dictionary["name"] {
self.name = name
}
if let dev = dictionary["dev"] {
self.dev = dev
}
}
}
class Service {
let databaseRef = FIRDatabase.database().reference()
func getData(completion: #escaping ((_ structure: MyStructure) -> Void)) {
databaseRef.child("Data").observeSingleEvent(of: .value, with: { snapshot in
if let value = snapshot.value as? [String: String] {
completion(MyStructure(with: value))
}
}) { (error) in
print(error.localizedDescription)
}
}
}
let service = Service()
override func viewDidLoad() {
super.viewDidLoad()
service.getData { myStructure in
DispatchQueue.main.async {
self.titleLabel.text = myStructure.name
self.devLabel.text = myStructure.dev
}
}
}
Of course it does. The getData() method makes an asynchronous call to the Firebase service. Meaning, the .observeSingleEvent method of firebase runs in a async queue as most code blocks that makes a service call. So the compiler reaches to getData() line, pushes the block to a different queue and continues to compile the next lines which are in the original queue. If you want those lines to run after you receive the response, you may add a closure to your getData() method as a parameter.
You can do something like this:
getData(completion: () -> Void) {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Data").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
self.myName = name
let dev = value?["dev"] as? String ?? ""
self.myDev = dev
completion()
}) { (error) in
print(error.localizedDescription)
}
}
override func viewDidLoad(){
super.viewDidLoad()
service.getData(completion: { Void in
configureLabel()
})
}

access a variable outside a function - swift

I'm trying yo access a variable outside a function, I tried to declare the variable outside the class but it keeps displaying the initial value in the declaration not the value inside the function, here is my code, I need to access databaseScore
func getDatabaseScore()-> Int{
let ref2 = FIRDatabase.database().reference().child("users").child("user").child((user?.uid)!)
ref2.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if var userDict = snapshot.value as? [String:Int] {
//Do not cast print it directly may be score is Int not string
var databaseScore = userDict["score"]
}
})
return databaseScore
}
As mentioned in the comment it's impossible to return something form a method containing an asynchronous task.
You need a completion block for example
func getDatabaseScore(completion: (Int?)->()) {
let ref2 = FIRDatabase.database().reference().child("users").child("user").child((user?.uid)!)
ref2.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let userDict = snapshot.value as? [String:Int] {
//Do not cast print it directly may be score is Int not string
completion(userDict["score"])
}
completion(nil)
})
}
getDatabaseScore() { score in
guard let score = score else { return }
// do something with unwrapped "score"
}
You're doing an async operation so getDatabaseScore returns before observeSingleEvent completes. You could look at something like this…
class MyClass {
var databaseScore: Int = 0
func getDatabaseScore() {
let ref2 = FIRDatabase.database().reference().child("users").child("user").child((user?.uid)!)
ref2.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let userDict = snapshot.value as? [String:Int] {
print(userDict["score"]) // Confirm you have the a value
self.databaseScore = userDict["score"]
}
}
}