Getting empty Array from Firestore Function (swift) - arrays

Hi this function is producing an unexpected result for me. I'm new to programming so I appreciate any help understanding this.
The first print statement prints second to the last print statement. I think its because maybe the call to Firebase is still waiting for results even though the function completed the operation? How can I make it so that the function doesn't return an empty array?
func readFromDatabase() -> [[String]] {
let db = Firestore.firestore()
db.collection("Letters").getDocuments { (snapshot, error) in
if error != nil {
print(error?.localizedDescription ?? "error")
} else {
for document in (snapshot?.documents)! {
// let fetchedData = document.data() //gets all data from all documenters unorganized
let firstLetter = document.get("1") as! String
let secondLetter = document.get("2") as! String
let thirdLetter = document.get("3") as! String
let fourthLetter = document.get("4") as! String
let fifthLetter = document.get("5") as! String
let sixthLetter = document.get("6") as! String
let seventhLetter = document.get("7") as! String
let eighthLetter = document.get("8") as! String
let organizedData = [firstLetter, secondLetter, thirdLetter, fourthLetter, fifthLetter, sixthLetter, seventhLetter, eighthLetter]
self.databaseData = organizedData
self.make2darray.append(self.databaseData) //makes 2darray
} //closing fetch
} //closing else statement
print("printing", self.make2darray) // works ok
} //closing snapshot
print("printing outside snapshot", self.make2darray) //returns empty array
return self.make2darray //returns empty array
} //closing function
UPDATE: I've made the following changes to the code after reading and trying a few things. This should work as far as I understood what I read but I'm still getting an empty array.
func readFromDatabase(completion: #escaping ([[String]]) -> Void) {
let db = Firestore.firestore()
let dispatchGroup = DispatchGroup()
db.collection("Letters").getDocuments { (snapshot, error) in
if error != nil {
print(error?.localizedDescription ?? "error")
} else {
dispatchGroup.enter()
for document in (snapshot?.documents)! {
// let fetchedData = document.data() //gets all data from all documenters unorganized
let firstLetter = document.get("1") as! String
let secondLetter = document.get("2") as! String
let thirdLetter = document.get("3") as! String
let fourthLetter = document.get("4") as! String
let fifthLetter = document.get("5") as! String
let sixthLetter = document.get("6") as! String
let seventhLetter = document.get("7") as! String
let eighthLetter = document.get("8") as! String
let organizedData = [firstLetter, secondLetter, thirdLetter, fourthLetter, fifthLetter, sixthLetter, seventhLetter, eighthLetter]
self.databaseData = organizedData
self.make2darray.append(self.databaseData) //makes 2darray
} //closing fetch
dispatchGroup.leave()
} //closing else statement
//print("printing", self.make2darray) // works ok
dispatchGroup.notify(queue: .main){
completion(self.make2darray)
}
} //closing snapshot
} //closing function

you may be calling the print statement too early, try calling it after the function has run
override func viewdidload() {
super.viewDidLoad()
readFromDatabase()
print (make2darray)
}

Okay to anyone looking for an answer to this in the future. Here is how I was able to resolve the issue. I had to create an observer with a notification key.
The notification key was created as a variable right under the class declaration like this: let notificationKey = "#####". The hash would be a unique key like your website name or something.
In the viewDidLoad, I called createObserver() and called readFromDatabase() right after.
What is happening is that while the readFromDatabase() function is getting the data, the observer is waiting to hear a signal from the Notification Center. When the work is complete the Notification Center triggers the key we created that tells the observer the process is complete. That means now that "make2darray" has the complete data records pulled from the database and is ready for use. We must also remove the observer right after to not cause any issues later.
This code is crude I'm sure, but it works. I'm a new programmer, so use whatever way gets the same results for you. Good luck to you.
func createObserver(){
//listening to hear if the database call is complete
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.readFromDatabase), name: NSNotification.Name(rawValue: notificationKey), object: nil)
}
#objc func readFromDatabase() {
let db = Firestore.firestore()
db.collection("Letters").getDocuments { (snapshot, error) in
if error != nil {
print(error?.localizedDescription ?? "error")
} else {
for document in (snapshot?.documents)! {
// let fetchedData = document.data() //gets all data from all documenters unorganized
let firstLetter = document.get("1") as! String
let secondLetter = document.get("2") as! String
let thirdLetter = document.get("3") as! String
let fourthLetter = document.get("4") as! String
let fifthLetter = document.get("5") as! String
let sixthLetter = document.get("6") as! String
let seventhLetter = document.get("7") as! String
let eighthLetter = document.get("8") as! String
let organizedData = [firstLetter, secondLetter, thirdLetter, fourthLetter, fifthLetter, sixthLetter, seventhLetter, eighthLetter]
self.databaseData = organizedData
self.make2darray.append(self.databaseData) //makes 2darray
} //closing fetch
} //closing else statment
//Notifies the observer that the process is complete
NotificationCenter.default.post(name: NSNotification.Name(rawValue: self.notificationKey), object: nil)
//Removing the observer
NotificationCenter.default.removeObserver(self)
} //closing database call
//verifying all entries are present
//print("printing outside database call", self.make2darray.count)
} //closing function

Related

Read Array from Firebase - Swift

I am writing a function that allows me to read the data inside the Database. I can read the String from Firebase but I cannot read the arrays. I tried like this but it doesn't work:
func fetchData(collection: String) {
DatabaseFirestore.collection(collection).addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.example = documents.map { (queryDocumentSnapshot) -> Example in
let data = queryDocumentSnapshot.data()
let ex1 = data["ex1"] as? String ?? "" // it is not an array in the database
let ex2 = data["ex2"] as? String ?? "" // is an array in the database
return Example(ex1: ex1, ex2: ex2)
}
}
}
How could I change this line so that it reads the array inside the database?
let ex2 = data["ex2"] as? String ?? ""
Casting as [String] instead of String might fix the issue

You also could check this answer - it could be helpful when retrieving arrays and dictionaries from Firebase

TableView is not populating after populated from loaded array

I have read through the similar questions, but am still stuck.
I have an array in a separate swift file called getSkills. It connects to a Firebase database and returns the skills into a variable/array called mySkills.
func getSkills(owner: String) {
mySkills = []
let db = Firestore.firestore()
db.collection("Skills").order(by: "SkillName").getDocuments { (querySnapshot, error) in
if let e = error {
print(e)
//TO DO: show error on screen
} else {
if let safeDocs = querySnapshot?.documents {
for doc in safeDocs {
let data = doc.data()
if let sklName = data["SkillName"] as? String,
let sklType = data["SkillType"] as? String,
let sklLevel = data["SkillLevel"] as? String,
let sklIsTimed = data["isTimed"] as? Bool,
let sklSecs : Int = data["Seconds"] as? Int,
let sklOwner = data["Owner"] as? String,
let sklID = doc.documentID as? String {
let newSkill = Skill(id: sklID, name: sklName, type: sklType, isTimed: sklIsTimed, seconds: sklSecs, level: sklLevel, owner: sklOwner)
mySkills.append(newSkill)
}
}
}
}
}
}
In a separate swift view controller, I am calling the getSkills function in ViewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
skillList.dataSource = self
skillList.delegate = self
DispatchQueue.main.async {
getSkills(owner: getUserID())
print(mySkills.count)
self.skillList.reloadData()
}
However, when the View Controller displays in the simulator the tableview is not populated with the list of Skills.
I can get the issue to work with a "LoadData()" function in the View Controller. However, I'm trying to build a reusable getSkills function that I can call in multiple locations as opposed to writing the same code over and over.
I've tried the following things:
Dispatch Main Queue in the getSkills function
Dispatch main queue in the ViewController itself
Moving the Dispatch main queue lines to the ViewWillAppear function on the ViewController.
wrapping the mySkills.append in the getSkills function within Dispatch Main Queue
Any ideas?

Swift - Populating an array with NSDictionary String data

I have successfully set up a session to pull json data from a site - it works. However, when I start to populate my array with the data the array is not populating the information. Please help.
Here is my Person class:
class Person {
var name1 = "name"
var info1 = "info"
init(name1: String, info1: String) {
self.name1 = name1
self.info1 = info1
}
}
Here is my setUpPerson function:
func setUpPerson() {
let url = NSURL(string: "http://www.my-sites-jsondata")
if url != nil {
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
let urlError = false
if error == nil {
var urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding) as NSString!
var data: NSData = urlContent.dataUsingEncoding(NSUTF8StringEncoding)!
do {
let jsonObject = try (NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSArray)!
var index1 = 0
while index1 < jsonObject.count {
var maindata = (jsonObject[index1] as! NSDictionary)
var nameA = maindata["Name"] as! String
var infoA = maindata["Info"] as! String
var data1 = Person(name1: nameA, info1: infoA)
self.arrayOfPersons.append(data1)
index1++
}
} catch {
print(urlError)
}
}
})
task.resume()
}
}
Your code looks all good to me.
To me your self.arrayOfPersons is also being set (I just tested it with my test app).
You're probably attempting to print it before it's been set in the async dataTaskWithURL block.
An async block performs in the background, so as the code written immediately after your block executes on the main thread, the code within the async block will not yet be complete.
To me, the code has definitely executed. But because the async code hasn't finished executing yet, you are not seeing it set.
You will have to wait until after the async code executes before checking for the new value.
You can try like this:
dispatch_async(dispatch_get_main_queue(), ^{
// Set your data on property here
});
I was able to successfully complete this project. The answer came in two fold...
I needed dispatch_async(dispatch_get_main_queue()
reloadData()

Swift Array: [AnyObject] vs. [xxxxxxxxClass] and the method "append"

Here's my code. You don't need to look at all of it. I added comments where I'm confused:
class ProductData: NSObject {
var title = ""
var icon = ""
private init(dict: NSDictionary){
title = dict["title"] as! String
icon = dict["icon"] as! String
super.init()
}
class func getTheData(fromJSONPath JSONPath: String) -> [ProductData] {
let JSONData = NSData(contentsOfFile: JSONPath)!
var JSONArray = [[String : AnyObject]]()
do {
JSONArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.MutableContainers) as! [Dictionary]
} catch { print("error")}
-----------------------------------------------------------------------------------------
//↓↓↓↓↓↓↓↓↓ different: data = "[AnyObject]()" or "[ProductData]()" ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
var data = [AnyObject]()
// var data = [ProductData]()
for d in JSONArray {
data.append(ProductData(dict: d))
}
return data as! [ProductData]
// return data
//↑↑↑↑↑↑↑↑↑ and here: return "data as! [ProductData]" or "data" ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
}
I use "var data = [ProductData](), retun data" first. There's no error or warning, but when I run my app, and run to the code data.append(ProductData(dict: d)), it crashes with the error: thread 1:exc_bad_access(code=1,address=0x10). What?!
I found a way to fix it: if I use var datas = [AnyObject]() and return datas as! [ProductData], it works very well.
I am so confused:
Why does [AnyObject] make the code OK?
When I use [ProductData], why does the code: data.append(ProductData(dict: d)) crash?
What is the different between [AnyObject] and [ProductData]?
Your original version works for me (screenshot) (only slightly modified for testing with my data). You shouldn't have to do this dance, something else is causing trouble.
I suggest cleaning up your class a bit and take advantage of Swift 2 using guard, map and error. It will be easier to debug and will work more efficiently anyway.
Here's an example. The only difference is that I'm using NSURL to access the data in my case and I've removed the icon value, but it's easy to change it back to your case.
class ProductData: NSObject {
var title = ""
private init(dict: [String : AnyObject]){
if let t = dict["title"] as? String { self.title = t }
super.init()
}
class func getTheData(fromJSONPath JSONPath: String) -> [ProductData] {
do {
// safely unwrap and typecast the values else return empty array
guard let url = NSURL(string: JSONPath),
let JSONData = NSData(contentsOfURL: url),
let JSONArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: [])
as? [[String : AnyObject]] else { return [] }
return JSONArray.map() { ProductData(dict: $0) }
} catch {
// this `error` variable is created by the `catch` mechanism
print(error)
// return empty array if unkown failure
return []
}
}
}
let test = ProductData.getTheData(fromJSONPath: "http://localhost:5678/file/test.json")
Note: I'm sure you know it but just in case for the readers, NSData(contentsOf... is a synchronous function, so it will block the main thread (unless executed from a background thread). It's better practice to use asynchronous functions when possible.

Swift: Looping through a Dictionary Array

I'm struggling to loop through an array of dictionary values returned from a web service call.
I've implemented the following code and I seem to be encountering a crash on running.
I'd also like to store the results into a custom Struct. Really having difficulty achieving this and the answers on here so far haven't worked. Would be grateful if someone is able to help.
let nudgesURLString = "http://www.whatthefoot.co.uk/NUDGE/nudges.php"
let nudgesURL = NSURL(string: nudgesURLString)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(nudgesURL!, completionHandler: {data, response, error -> Void in
if error != nil {
println(error)
} else {
let nudgesJSONResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
let nudges: NSDictionary = nudgesJSONResult["nudges"] as NSDictionary
if let list = nudgesJSONResult["nudges"] as? [[String:String]] {
for nudgeDict in list {
let location = nudgeDict["location"]
println(location)
}
}
}
})
task.resume()
}
NOTICE
This answer was written using Swift 1.2 and as such, there may be some slight stylistic and syntax changes required for the answer to work depending on your current Swift system.
Answer -- Swift 1.2
This line is crashing your code:
let nudges: NSDictionary = nudgesJSONResult["nudges"] as NSDictionary
You're forcing a cast that Swift can't handle. You never make it to your for-loop.
Try changing your code to look more like this:
let nudgesURLString = "http://www.whatthefoot.co.uk/NUDGE/nudges.php"
let nudgesURL = NSURL(string: nudgesURLString)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(nudgesURL!, completionHandler: {data, response, error -> Void in
if error != nil {
println(error)
} else {
let nudgesJSONResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as [String : AnyObject]
if let nudges = nudgesJSONResult["nudges"] as? [[String : String]] {
for nudge in nudges {
let location = nudge["location"]
println("Got location: \(location)")
println("Got full nudge: \(nudge)")
}
}
}
})
task.resume()
Thanks,
I created the following Struct which stored the data, and also lets me create dictionaries in the view controller for a particular index.
struct NudgesLibrary {
var location: NSArray?
var message: NSArray?
var priority: NSArray?
var date: NSArray?
var nudges: NSArray?
init(nudgesObject: AnyObject) {
nudges = (nudgesObject["nudges"] as NSArray)
if let nudges = nudgesObject["nudges"] as? NSArray {
location = (nudges.valueForKey("location") as NSArray)
message = (nudges.valueForKey("message") as NSArray)
priority = (nudges.valueForKey("priority") as NSArray)
date = (nudges.valueForKey("date") as NSArray)
}
}
}

Resources