Global variable appears to lose value (swift) - 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()
})
}
}

Related

Turning a firebase database value into a variable 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)
})
}

How can I pick three random elements out of a dictionary in Swift 4.1

I am having a problem picking three random elements out of a dictionary.
My dictionary code:
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! DataSnapshot
var dict = childSnap.value as! [String: Any]
}
})
You can use an array if keys are integers.
if you want to use a dictionary only then below mentioned code might be helpful for you
var namesOfPeople = [Int: String]()
namesOfPeople[1] = "jacob"
namesOfPeople[2] = "peter"
namesOfPeople[3] = "sam"
func makeList(n: Int) -> [Int] {
print(namesOfPeopleCount)
return (0..<n).map { _ in namesOfPeople.keys.randomElement()! }
}
let randomKeys = makeList(3)
You can try this for older version Of Swift where randomElement() is not available
let namesOfPeopleCount = namesOfPeople.count
func makeList(n: Int) -> [Int] {
return (0..<n).map{ _ in Int(arc4random_uniform(namesOfPeopleCount)
}
#Satish answer is fine but here's one which is a bit more complete and selects a random user from a list of users loaded from Firebase ensuring a user is only selected once.
We have have an app with two buttons
populateArray
selectRandomUser
and we have a UserClass to store our user data for each user.
class UserClass {
var uid = ""
var name = ""
init(withSnapshot: DataSnapshot) {
let dict = withSnapshot.value as! [String: Any]
self.uid = withSnapshot.key
self.name = dict["Name"] as! String
}
}
and an array to store the users in
var userArray = [UserClass]()
When the populateArray button is clicked this code runs
func populateArray() {
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let user = UserClass(withSnapshot: snap)
self.userArray.append(user)
}
print("array populated")
})
}
and then to select a random user use this code.
func selectRandomUser() {
if let someUser = userArray.randomElement() {
print("your random user: \(someUser.name)")
let uid = someUser.uid
if let index = userArray.index(where: { $0.uid == uid } ) {
userArray.remove(at: index)
}
} else {
print("no users remain")
}
}
This code ensures the same user is not selected twice. Note that this is destructive to the array containing the users so if that's unwanted, make a copy of the array after it's populated and work with that.

How to work with Firebase Asynchronously? Database reading giving odd results

I have written the following function to search through my Firebase database and I have also looked into using debug statements and tested with breakpoints to see this function is pulling the correct data and it is. But when I return the array at the end, the array is empty. As far as I understand this is due to the asynchronous nature of firebase. The function is getting to the end before the data is being added to the array. How do I fix this so it can work as intended, I want to return an array of items which I can then use for other functions.
static func SearchPostsByTags(tags: [String]) -> [Post]{
var result = [Post]()
let dbref = FIRDatabase.database().reference().child("posts")
dbref.observeSingleEvent(of: .value, with: { snap in
let comps = snap.value as! [String : AnyObject]
for(_, value) in comps {
let rawTags = value["tags"] as? NSArray
let compTags = rawTags as? [String]
if compTags != nil {
for cTag in compTags! {
for tag in tags {
if (tag == cTag) {
let foundPost = Post()
foundPost.postID = value["postID"] as! String
foundPost.title = value["title"] as! String
result.append(foundPost)
}
}
}
}
}
})
return result
}
}
You are returning your array before the async call ends. You should fill your array inside the async call and call then another method, which provides the results.
static func SearchPostsByTags(tags: [String]) {
let dbref = FIRDatabase.database().reference().child("posts")
dbref.observeSingleEvent(of: .value, with: { snap in
let comps = snap.value as! [String : AnyObject]
var result = [Post]()
for(_, value) in comps {
let rawTags = value["tags"] as? NSArray
let compTags = rawTags as? [String]
if compTags != nil {
for cTag in compTags! {
for tag in tags {
if (tag == cTag) {
let foundPost = Post()
foundPost.postID = value["postID"] as! String
foundPost.title = value["title"] as! String
result.append(foundPost)
}
}
}
}
}
// Call some func to deliver the finished result array
// You can also work with completion handlers - if you want to try have a look at callbacks / completion handler section of apples documentation
provideTheFinishedArr(result)
})
}

Return two arrays from observeSingleEvent

I have questions and answers in Firebase Realtime Database, which I need to be downloaded and put in to arrays. I have created a function which takes a parameter and should return two arrays
let data = fetchQuestions(category: "Animals")
qAni = data.ques
aAni = data.ans
fetchQuestions method:
func fetchQuestions(category: String) -> (ques: [String], ans: [String]) {
var q = [String]()
var a = [String]()
ref.child("QA/Category").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? [String: Any]
for (k, v) in value! {
if k == category {
let QA = v as! NSArray
for i in QA {
let question = i as! [String: String]
q.append(question["question"]!)
a.append(question["answer"]!)
}
return (q, a) ** Unexpected non-void return value in void function
}
}
})
}
I have tried to use dispatch group, but then I receive a different error.
You are currently returning from the completion block of observeSingleEvent and its return type is void, What you need is you need to create completionHandler with your fetchQuestions. Now with Swift instead of using NSArray use Swift native array. Also instead of maintaining two array maintain one array with custom objects of struct or class.
So first create one struct like this.
struct Category {
let question: String
let answer: String
}
Now make completionHandler with your fetchQuestions like this way.
func fetchQuestions(category: String, completion: #escaping([Category]) -> Void) {
var array = [Category]
ref.child("QA/Category").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? [String: Any]
for (k, v) in value! {
if k == category {
let QA = v as! [[String:String]]
for i in QA {
let que = i["question"]!
let ans = i["answer"]!
array.append(Category(question: que, answer: ans))
}
}
}
completion(array)
})
}
Now call fetchQuestions this way.
self.fetchQuestions(category: "Animals") { categories in
//access categories here
print(categories)
}
Note: Instead of comparing value in for loop you can also use queryEqual(toValue:) with your request to get specific result check this SO thread for more details on it.
Its because you are returning a value from a closure instead you should use different closure for that. Try it:
func fetchQuestions(category: StringcompletionBlock:#escaping (_ ques:[String], _ ans :[String]) -> ()) {
var q = [String]()
var a = [String]()
ref.child("QA/Category").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? [String: Any]
for (k, v) in value! {
if k == category {
let QA = v as! NSArray
for i in QA {
let question = i as! [String: String]
q.append(question["question"]!)
a.append(question["answer"]!)
}
}
return completionBlock(q,a)
}
})
}
#escaping is required : when the closure is passed as an argument to
the function, but is called after the function returns.

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"]
}
}
}