Swift function returning empty data - swift

i'm new to coding in Swift, I have been looking around for some time for a solution to this issue and as of yet unable to find anything that solves my problem.
I am using a function to return some data from Firebase. After many errors and much research, I have managed to get the function 'working' without throwing errors, but the data that is returned is blank. Essentially I am trying to return multiple values and hold them in an array when I call the function.
The data is returning fine from Firebase, when I print out the variable just after setting it, it will print out correctly but when I do the same just before returning the function, it returns blank. If I try to return the function just after setting the data, I get the error "Unexpected non-void return value in void function".
Here is the full code:
func loadClientData(client_id: String) -> (address_1: String, address_2: String, city: String, company_name: String, contact_name: String, county: String, email: String, phone: String, postcode: String, country: String) {
let db = Firestore.firestore()
let userID : String = (Auth.auth().currentUser!.uid)
print("\(userID)/clients/existing/\(client_id)")
let docRef = db.collection("\(userID)/clients/existing/").document(client_id)
var address_1 = ""
var address_2 = ""
var city = ""
var company_name = ""
var contact_name = ""
var county = ""
var email = ""
var phone = ""
var postcode = ""
var country = ""
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let data = document.data()
address_1 = data?["address_1"] as? String ?? ""
address_2 = data?["address_2"] as? String ?? ""
city = data?["city"] as? String ?? ""
company_name = data?["company_name"] as? String ?? ""
contact_name = data?["contact_name"] as? String ?? ""
county = data?["county"] as? String ?? ""
email = data?["email"] as? String ?? ""
phone = data?["phone"] as? String ?? ""
postcode = data?["postcode"] as? String ?? ""
country = data?["country"] as? String ?? ""
print("Company name is \(company_name)") // <---- THIS prints out the company name
} else {
print("client does not exist")
return
}
}
print("Company name is \(company_name)") // <---- THIS prints the company name as blank
return (address_1: address_1, address_2: address_2, city: city, company_name: company_name, contact_name: contact_name, county: county, email: email, phone: phone, postcode: postcode, country: country)
}
This is being called like so:
let companyInfo = loadClientData(client_id: self.items[indexPath.item].company)
print(companyInfo)
And prints out the following:
(address_1: "", address_2: "", city: "", company_name: "",
contact_name: "", county: "", email: "", phone: "", postcode: "",
country: "")
Thanks in advance for your input.

There are many questions on this site that are fundamentally the same as yours, and you should take a look at them. To avoid redundancy, I'll explain in simple terms, but I urge you to read up on similar questions.
In a nutshell, what's happening is that loadClientData fetches data asynchronously, meaning that it happens in the background while other stuff happens in the foreground. The foreground stuff includes your code that calls loadClientData and prints it out. The foreground stuff doesn't wait for the background stuff to finish.
You're getting nothing because the background stuff isn't finished processing by the time you print(companyInfo).
Real world example:
Your mom asks you to go buy a lemon for a dish she is cooking for dinner.
She starts cooking, but she has no lemon!
Why? Because you haven't yet returned from the supermarket, and your mom didn't wait.
If you want to make the foreground stuff wait, you have a lot of options, which you can consider by looking at other similar questions.

You need to refactor your function to not return anything, and take a completion handler. The completion handler is a block of code that the caller passes to your function. Your function will call the comletion handler once the results are available. This is a common coding pattern in Swift, and your firebase getDocument() call is written to use a completion handler.
Search on Swift completion handler design pattern to learn more.
**Note:
Using a completion handler is only one way to do this. You could also use the delegate design pattern, but completion handers are a more modern way of handling async events, and the way I would suggest handling your problem.

Related

Print out properties that are not equal between two objects

Say I have a User class with three properties: name, email, and fruits. What's the most efficient way to determine the differences, and print them out in a dictionary alongside the property name?:
struct User: Equatable {
let name: String
let email: String
let fruits: [Fruit]
}
// Old user
let user = User(name: "Jane", email:"ja#ne.com", fruits: [.banana, .apple])
// Updated user
let updatedUser = User(name: user.name, email: user.email, fruits: [.apple, .peach])
// Looking for help writing a function that can efficiently find the changes + format them into a dictionary for Firebase etc:
let updatedProperties = updatesBetween(old: user, new: updatedUser)
// Output:
//["fruits": ["apple", "peach"]]
Take advantage of the fact that these objects are Equatable and check for their equality right off the bat before proceeding, and then go property by property and look for differences. Keep in mind that arrays care about order so [.apple, .banana] will be different from [.banana, .apple]. If you don't care about order then just sort them both before comparing (this may require additional steps depending on the contents of the array). You can also consider translating the arrays into sets before comparing (if the arrays don't contain duplicates).
func getDifferencesBetween(old: User, new: User) -> [String: Any] {
guard old != new else {
return [:]
}
var differences: [String: Any] = [:]
if old.name != new.name {
differences["name"] = new.name
}
if old.email != new.email {
differences["email"] = new.email
}
if old.fruits != new.fruits {
differences["fruits"] = new.fruits
}
return differences
}

SwiftUI, Firestore get array of object within object

I am working on a recipe-app connected to firestore and have trouble reading the data saved in the database. I save a recipe that consists of title, id etc but it also contains an array of ingredients. This array is a struct containing id, name and amount. I am able to get the recipe object but the array of ingredients is empty. This is how is get the recipe
private func listenForRecipes() {
db.collection("recipe").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.recipes = documents.map { queryDocumentSnapshot -> RecipePost in
let data = queryDocumentSnapshot.data()
let title = data["title"] as? String ?? ""
let steps = data["steps"] as? [Step] ?? []
let ingredients = data["ingredients"] as? [Ingredient] ?? []
let serves = data["serves"] as? Int ?? 0
let author = data["author"] as? String ?? ""
let authorId = data["authorId"] as? String ?? ""
let category = data["category"] as? String ?? ""
let image = data["image"] as? String ?? ""
print("\(ingredients)")
return RecipePost(title: title, steps: steps, ingredients: ingredients, serves: serves, author: author, authorId: authorId, category: category, image: image)
}
}
}
Thankful for any help.
The data that you're getting from Firebase is coming back to you in the form of a [String:Any] dictionary. Your current code is taking those dictionary keys (title, author, etc) and doing optional casts (the as?), telling the system "if this data is actually a String, then set my variable to that value. If not (??), here's the default value to use instead"
The problem comes when you introduce custom types. The system doesn't inherently know that your item is an Ingredient or Step. So, the cast fails, and you get the default value of [].
You have two options:
Use a custom type for your entire document (see Firebase documentation on this here: https://firebase.google.com/docs/firestore/query-data/get-data#swift_3). This SO question is also relevant: How to convert document to a custom object in Swift 5?
Convert the [String:Any] dictionary (or array of dictionaries as it may be in this case) yourself. First step might be to print data["ingredients"] to the console to see what it really has in it. Without being able to see what you actually have in Firestore, Let's assume it is a [[String:Any]] (an array of dictionaries). Then your conversion might look like this:
let ingredients = (data["ingredients"] as? [[String:Any]]).map { item in
return Ingredient(id: item["id"] as? String ?? "", name: item["name"] as? String ?? "", amount: item["amount"] as? String ?? "")
}
You can also experiment with using Codable, which could allow you to automate some of this process, say with JSONDecoder to do some of the work for you. Relevant SO: How can I use Swift’s Codable to encode into a dictionary?

Can't extract Dates or Timestamps from Firestore DocumentSnapshot

I'm retrieving a set of documents of events that contains Strings, Dates and a Timestamp. Strings are no problem but recently I added Dates and Timestamp to my model they always get returned as nil, which I have set as default, even though I can clearly see in the Firebase console they are stored correctly.
init setup for DocumentSnapshot retrieval
init?(snapshot:DocumentSnapshot){
self.eventId = snapshot.get("eventId") as? String ?? "No event Id"
self.byId = snapshot.get("byId") as? String ?? "No uid"
self.adminUser = snapshot.get("adminUser") as? String ?? "No admin user"
//self.eventCreated = snapshot.get("eventCreated") as? Timestamp ?? "No Event Created Date"
self.eventName = snapshot.get("eventName") as? String ?? "No Event Name"
self.eventLocation = snapshot.get("eventLocation") as? String ?? "No Location"
self.eventStart = snapshot.get("eventStart") as? Date ?? nil
self.eventEnd = snapshot.get("eventEnd") as? Date ?? nil
}
Results from DocumentSnapshot
adminUser: "",
byId: "juYtTP509rhXYPd433",
eventCreated: nil, //Timestamp retrieved as nil
eventId: "gC2RVdUuB9CD66JEYM18",
eventName: "test",
eventLocation: "",
eventStart: nil, //Date retrieved as nil
eventEnd: nil, //Date retrieved as nil
Events Model
struct Event {
var adminUser = ""
var byId = ""
var eventCreated:Timestamp?
var eventId = ""
var eventName = ""
var eventLocation = ""
var eventStart:Date? = nil
var eventEnd:Date? = Date(timeIntervalSince1970: 0)
Please let me know if I need to add the method here for better context?
Firestore doesn't have an internal Date type. Internally, it will create a Timestamp when it receives either Dates or Timestamp as input, so that's what your code should expect coming out of DocumentSnapshot in either case (never a Date).

A type of bodiless closure with {}()?

I see examples of variable initialisation code that can contain any number of statements, for example:
var _globalCounter = 0
let instanceCount: Int = { _globalCounter++ }()
print(instanceCount.dynamicType) // Int
and a full code sample:
struct User {
let name: String
let company: String
let login: String
let password: String
static let database: Dictionary<String, User> = {
var theDatabase = Dictionary<String, User>()
for user in [
User(name: "John Appleseed", company: "Apple", login: "japple", password: "foo"),
User(name: "John Hennessy", company: "Stanford", login: "hennessy", password: "foo"),
User(name: "Bad Guy", company: "Criminals, Inc.", login: "baddie", password: "foo")
] {
theDatabase[user.login] = user
}
return theDatabase
}()
}
What's going on? Is it assignment from a closure like assigning a value from a function call?
These samples are copied form cs 193p fall 2014 class, are these considered to be good practice in terms of complex variable initialisation compared to putting stuff in init?
Please note that here in the second example the variable is static, so effectively this is a thread safe initialisation code?
I'm #learning swift and #confused.
Yes, a closure is just a function. Like any other function, you call it by passing in the arguments inside parentheses. If it is a function that takes no arguments, then you call it with empty parentheses.
let one: Int = { return 1 }()
It's actually as simple as that :)

Multiple unwrap of optionals

I have an object, let's say its called "Event". Event has several optionals.
struct Event {
var name: String!
var location: String?
var attendees: [String]?
var dresscode: String?
var startDate: NSDate!
var endDate: NSDate?
var description: String {
return "Name: \(name). Location: \(location). Attendees: \(attendees). Dresscode: \(dresscode). Start date: \(startDate). End date: \(endDate)."
}
}
When I call for the description, it will return a string, and depending on if they exist or not the optional values will return nil or "Optional(Event name)". I want the option for some of the properties to be nil and return the unwrapped value if they exist.
I have looked at this option:
var description: String {
switch (location, attendees, dresscode, endDate) {
//All available
case let (.Some(location), .Some(attendees), .Some(dresscode), .Some(endDate)):
return "Name: \(name). Location: \(location). Attendees: \(attendees). Dresscode: \(dresscode). Start date: \(startDate). End date: \(endDate)."
case let (.None, .Some(attendees), .Some(dresscode), .Some(endDate)):
return "Name: \(name). Location: Not Set. Attendees: \(attendees). Dresscode: \(dresscode). Start date: \(startDate). End date: \(endDate)."
default: return "Something."
}
This works, but for me to cover all the cases it is going to take forever. It may contain hundreds of cases.
So my question is: Is there an easier way to do this?
Return nil if not available, and unwrap if it is.
Thank you!
You’re in need of the nil-coalescing operator, ??:
// s will be Not Set if name == nil,
// unwrapped value of s otherwise
let s = name ?? "Not set"
You can then use that inside the string interpolation:
var description: String {
let notSet = "Not set"
let attendeesString = attendees.map { ",".join($0) }
return "Name: \(name ?? notSet). Location: \(location ?? notSet). Attendees: \(attendeesString ?? notSet). Dresscode: \(dresscode ?? notSet). Start date: \(startDate ?? notSet). End date: \(endDate ?? notSet)."
}
Two things to note - you can’t put quotes in string interps so you have to create a notSet variable (of course, you can use multiple different default values depending on your need). And you can’t use arrays in them either so need to convert the array to a string (in this case by joining entries with a comma – that other map is optional map – if the array is non-nil, return an optional string, otherwise nil).