Properly configuring closure to capture results - ios

I am writing some custom store methods for in app purchases; sort of a wrapper for SwiftyStore. The problem I'm running into is the inability to get the results from the closures before they exit.
Any suggestions on how to properly set them up? IE: Closures...
I have a function that checks for an existing subscription and returns true if it finds one in firebase, if it doesn't then it goes out to the apple store to verify a previously purchased subscription:
func checkSubscription() -> Bool {
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
} else {
// we have a purchase, verify its not expired.
print("Purchased")
RetVal = true
}
}) { (error) in
print(error.localizedDescription)
}
return RetVal
}
The problem here is its dropping down to the return RetVal before the closure is complete so the function could be returning an invalid value. Not sure how I can fix this in the current setup, but any suggestions or pointers would be appreciated.

To expand on Tom's comment, if you want to return a result when the nested asynchronous function is complete, you could pass in a completion handler closure that uses the Result type that Swift offers like the following:
func checkSubscription(completion: #escaping (Result<Bool, Error>) -> Void) {
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
} else {
// we have a purchase, verify its not expired.
print("Purchased")
RetVal = true
}
completion(.success(RetVal))
}) { (error) in
print(error.localizedDescription)
completion(.failure(error))
}
}
Calling the function using this type of completion handler would look something like this:
checkSubscription { (result) in
switch result {
case .success(let boolValue):
// do something with resulting boolean
break
case .failure(let error):
// do something with resulting error
break
}
}

func checkSubscription(completion: (_ scuess:Bool) ->()){
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
completion(false)
} else {
// we have a purchase, verify its not expired.
print("Purchased")
completion(true)
}
}) { (error) in
print(error.localizedDescription)
completion(false)
}
return RetVal
}
call completion(true) whenever your retValue supposed to be true and completion(false) whenever your retValue supposed to be true
Then call this function this way:
checkSubscription { (sucuess) in
if(sucuess){
print("OK")
}else{
print("BAD")
}
}

Related

How can I validate textField input with a Firestore query call?

So my goal is to validate a textfield by checking if that value is within any of the documents in the Firestore collection. So in my other validation function, I can return a String and show an alert with the error like so:
func validateFields() -> String? {
if nameTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
emailTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
passwordTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
schoolIDTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
currentGradeTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""{
showAlert(title: "Missing Fields", message: "Please fill in all fields.")
return "Issue With Fields"
}
let cleanedPassword = passwordTextF.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let properGradeSelected = currentGradeTextF.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let validSchoolID = schoolIDTextF.text!.trimmingCharacters(in: .whitespacesAndNewlines)
if Utilities.isPasswordValid(cleanedPassword) == false {
showAlert(title: "Invalid Password", message: "Please ensure that your password contains 8 characters, contains a special character and a number as well.")
return "Issue With Password"
}
if Utilities.isGradeValid(properGradeSelected) == false {
showAlert(title: "Invalid Grade", message: "Please ensure that your current grade is valid.")
return "Issue With Grade Input"
}
if Utilities.isSchoolIDValid(validSchoolID) == false {
showAlert(title: "Invalid School ID Format", message: "The School ID entered has the incorrect format.")
return "Issue With School ID input."
}
return nil
}
Then I call it when the 'Sign Up' button is pressed like so:
#IBAction func signupPressed(_ sender: UIButton) {
//Validate the fields
let validationError = validateFields()
if validationError != nil {
return
} else {
//Create the user
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in ....
This works perfect. Now since I want to use a Firestore query in a function, I can't return the String like how I did in the other ones without getting errors, so I'm quite confused on how to go about doing this.
This is the function I have so far:
func determineIfIDIsValid() {
let schoolIDText = schoolIDTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
db.collection("school_users").whereField("school_id", isEqualTo: schoolIDText).getDocuments { (querySnapshot, error) in
guard error == nil else {
return
}
guard let query = querySnapshot?.isEmpty else { return }
if query == true {
//Show error alert
} else {
return
//Continue With Signup flow
}
}
}
I've tried declaring a variable before the query whether it be a String or Bool, changing the value after the query, and using logic to return a String but that didn't work either. How can I use this function to validate the specific field without getting any errors?
func determineIfIDIsValid(_ callback: #escaping (Bool) -> ()) {
let schoolIDText = schoolIDTextF.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
db.collection("school_users").whereField("school_id", isEqualTo: schoolIDText).getDocuments { (querySnapshot, error) in
guard error == nil else {
return
}
guard let query = querySnapshot?.isEmpty else { return }
if query == true {
//Show error alert
callback(true)
} else {
callback(false)
//Continue With Signup flow
}
}
}
Firebase calls are async. You need use closure
determineIfIDIsValid() { res in
if res {
//Show alert
} else {
//Continue With Signup flow
}

Issues with Swift's Combine framework CombineLatest

I went through the WWDC video of "Introducing Combine" where it was said that whenever a publisher value gets updated the CombineLatest gets called and updated. But the snippet I created works oddly.
class Mango {
var enableButton = false
#Published var userName = "admin"
#Published var password = "poweruser"
#Published var passwordAgain = "poweruser"
var validatePassword: AnyCancellable {
Publishers.CombineLatest($password, $passwordAgain).map { (password, reenterpass) -> String? in
print("Is Password Same to \(password)? :", password == reenterpass)
guard password == reenterpass else { return nil }
return password
}.eraseToAnyPublisher()
.map { (str) -> Bool in
print("In Map", str != nil)
guard str != nil else { return false }
return true
}.assign(to: \.enableButton, on: self)
}
init() {
validatePassword
}
func checkSub() {
print("1. Is password same? ->",enableButton)
password = "nopoweruser"
print("2. Is password same? ->",enableButton)
}
}
When I initialize and call the function checkSub() where the publisher 'password' is updated the CombineLatest does not get called. Why is it behaving oddly?
Input:
let mango = Mango()<br>
mango.checkSub()
Output:
Is Password Same to poweruser? : true
In Map true
1. Is password same? -> true
2. Is password same? -> true
It seems like the issue is with memory management. The validatePassword cancellable is autoreleased, meaning that the subscription is completed as soon as you create it, since you do not retain it. Make it a property instead of computed property, using lazy var and it should work fine.
lazy var validatePassword: AnyCancellable = {
Publishers.CombineLatest($password, $passwordAgain).map { (password, reenterpass) -> String? in
print("Is Password Same to \(password)? :", password == reenterpass)
guard password == reenterpass else { return nil }
return password
}.eraseToAnyPublisher()
.map { (str) -> Bool in
print("In Map", str != nil)
guard str != nil else { return false }
return true
}.assign(to: \.enableButton, on: self)
}()
With lazy you are retaining the cancellable which gets released only after the object is released. So, this should work properly.

altering a variable outside a closure

I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)

How do I call a function only after an async function has completed?

I have a function Admin that runs asynchronously in the background.
Is there a way to make sure that the function is completed before calling the code after it?
(I am using a flag to check the success of the async operation. If the flag is 0, the user is not an admin and should go to the NormalLogin())
#IBAction func LoginAction(sender: UIButton) {
Admin()
if(bool.flag == 0) {
NormalLogin()
}
}
func Admin() {
let userName1 = UserName.text
let userPassword = Password.text
let findTimeLineData2:PFQuery = PFQuery(className: "Admins")
findTimeLineData2.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if !(error != nil){
for object in objects as! [PFObject] {
let userName2 = object.objectForKey("AdminUserName") as! String
let userPassword2 = object.objectForKey("AdminPassword") as! String
if(userName1 == userName2 && userPassword == userPassword2) {
//hes an admin
bool.flag = 1
self.performSegueWithIdentifier("AdminPage", sender: self)
self.UserName.text = ""
self.Password.text = ""
break;
}
}
}
}
}
You need to look into completion handlers and asynchronous programming. Here's an example of an async function that you can copy into a playground:
defining the function
notice the "completion" parameter is actually a function with a type of (Bool)->(). Meaning that the function takes a boolean and returns nothing.
func getBoolValue(number : Int, completion: (result: Bool)->()) {
if number > 5 {
// when your completion function is called you pass in your boolean
completion(result: true)
} else {
completion(result: false)
}
}
calling the function
here getBoolValue runs first, when the completion handler is called (above code) your closure is run with the result you passed in above.
getBoolValue(8) { (result) -> () in
// do stuff with the result
print(result)
}
applying the concept
You could apply this concept to your code by doing this:
#IBAction func LoginAction(sender: UIButton) {
// admin() calls your code, when it hits your completion handler the
// closure {} runs w/ "result" being populated with either true or false
Admin() { (result) in
print("completion result: \(result)") //<--- add this
if result == false {
NormalLogin()
} else {
// I would recommend managing this here.
self.performSegueWithIdentifier("AdminPage", sender: self)
}
}
}
// in your method, you pass a `(Bool)->()` function in as a parameter
func Admin(completion: (result: Bool)->()) {
let userName1 = UserName.text
let userPassword = Password.text
let findTimeLineData2:PFQuery = PFQuery(className: "Admins")
findTimeLineData2.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if !(error != nil){
for object in objects as! [PFObject] {
let userName2 = object.objectForKey("AdminUserName") as! String
let userPassword2 = object.objectForKey("AdminPassword") as! String
if(userName1 == userName2 && userPassword == userPassword2) {
// you want to move this to your calling function
//self.performSegueWithIdentifier("AdminPage", sender: self)
self.UserName.text = ""
self.Password.text = ""
// when your completion handler is hit, your operation is complete
// and you are returned to your calling closure
completion(result: true) // returns true
} else {
completion(result: false) // returns false
}
}
}
}
}
Of course, I'm not able to compile your code to test it, but I think this will work fine.

Swift 2: guard in for loop?

what is the correct way to use guard inside a for loop?
for (index,user) in myUsersArray.enumerate() {
guard user.id != nil else {
print("no userId")
//neither break / return will keep running the for loop
}
if user.id == myUser.id {
//do stuff
}
}
There are a few ways to make some conditionals:
You can put a condition for whole for. It will be called for each iteration
for (index, user) in myUsersArray.enumerate() where check() {}
for (index, user) in myUsersArray.enumerate() where flag == true {}
You can check something inside for and skip an iteration or stop the loop:
for (index, user) in myUsersArray.enumerate() {
guard check() else { continue }
guard flag else { break }
}
In your case I will be write something like this:
for (index, user) in myUsersArray.enumerate() {
guard let userId = user.id, userId == myUser.id else { continue }
// do stuff with userId
}
#Arsens answer is correct but I think this is easier to understand
let ints = [1,2,3,4,5]
for (index,value) in ints.enumerate() {
guard value != 1 else {
print("Guarded \(value)")
continue
}
print("Processed \(value)")
}
for (index,user) in myUsersArray.enumerate() {
guard let userId = user.id else {
print("no userId")
continue;
}
if userId == myUser.id {
//do stuff
}
}

Resources