For some reason when I am adding data to Firebase database through a form on my app it saves the data to the database but three times instead of just once as it's supposed to.
I can't quite figure out why because I have used this code before and it has worked fine...
Code:
#IBAction func createPostTapped(_ sender: UIButton) {
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: {
(snapshot) in
if let userDictionary = snapshot.value as? [String: AnyObject] {
for user in userDictionary {
if let username = user.value as? String {
if let game = self.gameTextField.text {
if let activity = self.activityTextField.text {
if let console = self.consoleTextField.text {
if let skill = self.skillTextField.text {
if let communication = self.communicationTextField.text {
if let lfglfm = self.lfglfmTextField.text {
if let description = self.descriptionTextView.text {
let postObject: Dictionary<String, Any> = [
"uid" : uid,
"username" : username,
"game" : game,
"activity" : activity,
"console" : console,
"skill" : skill,
"communication" : communication,
"lfglfm" : lfglfm,
"description" : description
]
Database.database().reference().child("posts").childByAutoId().setValue(postObject)
let alert = UIAlertController(title: "Success!", message: "Your post was added successfully.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
//code will run when ok button is pressed
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoggedInVC")
self.present(vc!, animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
}
}
}
}
}
}
}
}
}
})
}
}
If anyone has any idea why my code would be posting the data three times instead of once I would appreciate the help.
Thank you!
I am guessing it is this line of code:
for user in userDictionary {
Looks like you have 3 entries inside that node so
Database.database().reference().child("posts").childByAutoId().setValue(postObject)
actually executes 3 times.
Related
I am attempting to access a certain data tag and show an alert if it is not null in the following remote notification:
[AnyHashable("google.c.sender.id"): ************, AnyHashable("google.c.fid"): asdfjkl1234556, AnyHashable("aps"): {
alert = {
body = "Shipment is no longer available and has been removed from the app.
title = "Shipment ****** no longer available";
};
},
AnyHashable("gcm.message_id"): 1234567891234567,
AnyHashable("google.c.a.e"): 1,
AnyHashable("shipmentMessage"): ****** is no longer available and has been removed form the app.]
AnyHashable("shipmentMessage"): ****** is no longer available and has been removed form the app is what I am trying to access. I believe my code should not be calling this null:
if UIApplication.shared.applicationState == .active{
print("ACTIVE< CHECK > SHIPMENT MESSAGE : : : : : : \(String(describing: userInfo["shipmentMessage"] as? [AnyHashable:Any]))")
guard let arrAPS = userInfo["aps"] as? [String: Any] else { return }
guard let arrAlert = arrAPS["alert"] as? [String:Any] else { return }
if (userInfo["shipmentMessage"] as? [AnyHashable:Any]) != nil {
print("***********NOT NULL***************")
let strTitle:String = arrAlert["title"] as? String ?? ""
let strBody:String = arrAlert["body"] as? String ?? ""
let alert = UIAlertController(title: strTitle, message: strBody, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default) { action in
print("OK Action")
})
self.window?.rootViewController?.present(alert, animated: true)
} else {
print("The shipmentMessage was null")
}
}
Is it the way I am iterating to the shipment message? Any help would be appreciated
Just replace AnyHashable to string like this (userInfo["shipmentMessage"] as? [String:Any])
or copy paste this below code -
if (userInfo["shipmentMessage"] as? [String:Any]) != nil {
print("***********NOT NULL***************")
let strTitle:String = arrAlert["title"] as? String ?? ""
let strBody:String = arrAlert["body"] as? String ?? ""
let alert = UIAlertController(title: strTitle, message: strBody, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default) { action in
print("OK Action")
})
self.window?.rootViewController?.present(alert, animated: true)
} else {
print("The shipmentMessage was null")
}
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
Within my function to load users I'm able to retrieve a value. However, when I want to assign it to my variable outside the function it has nothing, as shown in the login function.
Load User Function
func loadUser(userid: String) -> User {
//print(userid)
let userid = "56ldZFJiv0dpfaABzo78"
var user = User()
let docRef = db.collection("users").document(userid)
docRef.getDocument { (document, error) in
if let document = document {
let first = document.data()!["first"] as! String
let last = document.data()!["last"] as! String
let position = document.data()!["position"] as! String
let company = document.data()!["company"] as! String
let email = document.data()!["email"] as! String
let address = document.data()!["address"] as! String
let userID = document.data()!["userID"] as! String
//Initalize user
user = User(userID: userID,
firstName: first,
lastName: last,
company: company,
address: address,
position: position,
email: email)
print(user.position)
} else {
print("Document does not exist")
}
}
return user
}
Login Function
//MARK: LOGIN
func login() {
Auth.auth().signIn(withEmail: emailField.text!, password: passwordField.text!) { (user, error) in
if error == nil{
//self.performSegue(withIdentifier: "loginToAdmin", sender: self)
//Load user
let loggedOnUser = self.loadUser(userid: Auth.auth().currentUser!.uid)
print(loggedOnUser.userID)
// let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
// let chatViewController = storyBoard.instantiateViewController(withIdentifier: "chatVC") as! UINavigationController
// self.present(chatViewController, animated: true, completion: nil)
}
else {
DispatchQueue.main.async{
//Display Alert Message if login failed
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
For the first function, I get a position value, as stated in the print statement.
For the second function, my variable, "loggedOnUser" is empty.
You need a completion as loadUser is asynchronous
func loadUser(userid: String,completion:#escaping(User?) ->()) {
//print(userid)
let userid = "56ldZFJiv0dpfaABzo78"
var user = User()
let docRef = db.collection("users").document(userid)
docRef.getDocument { (document, error) in
if let document = document {
let first = document.data()!["first"] as! String
let last = document.data()!["last"] as! String
let position = document.data()!["position"] as! String
let company = document.data()!["company"] as! String
let email = document.data()!["email"] as! String
let address = document.data()!["address"] as! String
let userID = document.data()!["userID"] as! String
//Initalize user
user = User(userID: userID,
firstName: first,
lastName: last,
company: company,
address: address,
position: position,
email: email)
print(user.position)
completion(user)
} else {
print("Document does not exist")
completion(nil)
}
}
}
Call
self.loadUser(userid: Auth.auth().currentUser!.uid) { res in
if let user = res {
print(user)
}
}
func teacherExists(teacherName: String) -> Bool
{
var dataBaseRef2: DatabaseReference!
dataBaseRef2 = Database.database().reference()
let teachersTableRef = dataBaseRef2.child("teachers")
self.teachersList.removeAll()
teachersTableRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
// teachersTableRef.observe(.value)
//{
// snapshot in
let teachersNamesDictionary = snapshot.value as? [String: Any] ?? [:]
for(key, _) in teachersNamesDictionary
{
if let teacherDict = teachersNamesDictionary[key] as? [String: Any]
{
if let teacher = Teacher(dictionary: teacherDict)
{
//print(teacher.teacher_name)
self.teachersList.append(teacher.teacher_name)
}
}
}
print(self.teachersList.count)
})
print("Outside \(self.teachersList)")
return false
}
Because Firebase APIs are all asynchronous. It would be bad for your app if they blocked your code path, because that could cause your app to hang indefinitely.
observeSingleEvent returns immediately, and the passed observer gets invoked some time later, whenever the data is finally ready. Execution continues on the next line, which prints to the console.
getting error upon calling teacherExists function
let OKAction = UIAlertAction(title: "OK", style: .default, handler:
{
(action: UIAlertAction!) ->Void in
let textfield = alert.textFields![0] as UITextField
newTeacherName = textfield.text!.uppercased()
if !(newTeacherName.isEmpty)
{
//checking if teacher already exists using function teacherExists
let exists = self.teacherExists(teacherName: newTeacherName, completion:
if exists == true //if duplicate teacher is found
{
let alert = UIAlertController(title: "Duplicate Teacher", message: "Teacher \(newTeacherName) has been added earlier", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
else
{
//add teacher to database here
let dict = ["teacher_name" : newTeacherName]
let newTeacher = Teacher(dictionary: dict)
let tableRef = self.dataBaseRef.child("teachers") //getting reference of node with name teachers
let recordRef = tableRef.childByAutoId() //creating a new record in teachers node
recordRef.setValue(newTeacher!.toDictionary())//adding data to new record in teachers node
}
}
})
You can use closure to callback after check for duplicate
func teacherExists(teacherName: String, completion: #escaping ((Bool) -> Void)) -> Void {
var dataBaseRef2: DatabaseReference!
dataBaseRef2 = Database.database().reference()
let teachersTableRef = dataBaseRef2.child("teachers")
self.teachersList.removeAll()
teachersTableRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
let teachersNamesDictionary = snapshot.value as? [String: Any] ?? [:]
for(key, _) in teachersNamesDictionary
{
if let teacherDict = teachersNamesDictionary[key] as? [String: Any]
{
if let teacher = Teacher(dictionary: teacherDict)
{
//print(teacher.teacher_name)
self.teachersList.append(teacher.teacher_name)
}
}
}
let exists = self.teachersList.contains(teacherName)
completion(exists)
})
}
And call function as below
teacherExists(teacherName: newTeacherName) { (exists) in
if exists {
// show alert
} else {
// add new teacher to db
}
}
Hope it help!
Up until two days ago my code was working fine with no problems, out of the blue my code begins returning nil when I know for a fact that the value is there within my Firebase node. I have not touched the code in weeks nor have made anychanges to it any time recently. I have recently upgraded my Xcode to 9 but still running Swift 3.
I have the value radiusDistanceNumber declared above my viewDidLoad() as
class viewController: UIViewController {
var radiusDistanceNumber: Int()
override func viewDidLoad()
super.viewDidLoad {
}
func radiusValue(){
let user = Auth.auth().currentUser
guard let uid = user?.uid else{
return
}
let ref = Database.database().reference().child("Users").child(uid)
ref.observeSingleEvent(of: .value, with: {snapshot in
print("this is the snapshot value \(snapshot.value)")
//returns correct value of 14
if let dictionary = snapshot.value as? [String: AnyObject] {
self.radiusDistanceNumber = dictionary["radiusDistance"] as? Int
print(self.radiusDistanceNumber)
//returns nil
if self.radiusDistanceNumber == nil {
//let the user know it may be an error in connection
let alertController = UIAlertController(
title: "Error",
message: "Data not loading properly, make sure you have a strong connection and try again", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Got it", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
} else{
// pass the value to the slider so the user can see the distance
let radiusDistanceNumberFloat = Float(self.radiusDistanceNumber!)
self.radiusSlider.value = radiusDistanceNumberFloat
self.radiusLabel.text = String(self.radiusSlider.value)
}
}
})
}
Again, this code was working weeks ago
I think you should make these changes in your code . You are currently declaring the radiusDistanceNumber incorrectly so
Replace
var radiusDistanceNumber: Int()
with
var radiusDistanceNumber = Int()
I think you should also
replace
if let dictionary = snapshot.value as? [String: AnyObject]
with
if let dictionary = snapshot.value as? [String: Any]
So I am really new to threading and I've been reading up on it all day. For some reason though the data isn't loading before other code executes
Basically I need all the values that have a key ["whatever"] to be filled into an array, which works in other places because I don't need to load it first. So i have checked and double checked the keys that I am updating do exist and the keys I am extracting do exist maybe not the values yet but the keys do.
The problem is the code goes to fast to through the method. How would I make the main thread wait untill my firebase has loaded the data I have tried it below but it does not seem to be working
here is my code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
// let them know to wait a second or the bet won't go through
var waitController = UIAlertController(title: "Please Wait", message: "You must wait for the bet to go through", preferredStyle: .alert)
self.present(waitController, animated: true, completion: nil)
//take away that bitches money
self.takeAwayMoney(self.amountBets[indexPath.row], completion: { (result: Bool?) in
guard let boolResult = result else {
return
}
if boolResult == true {
self.updateBet(indexPath.row, completion: {(result: String?) in
guard let resultRecieved = result else {
return
}
print(self.opposingUserNames)
//let delayInSeconds = 7.0 // 1
//DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
self.dismiss(animated: true, completion: nil)
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames!, preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
print(self.opposingUserNames)
self.amountBets.remove(at: indexPath.row)
self.tableView.reloadData()
print("Second")
print(self.opposingUserNames)
//}
})
} else {
return
}
})
//then delete that cell and do another pop up that says successful
// check if value is yes or no in the database
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}
The below function updates the values OpposingUsername and show
func updateBet(_ index: Int, completion: #escaping (_ something: String?) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
let values = ["OpposingUsername": self.userName,"Show": "no"]
self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
// now get the opposing username which is just the Username registered to that specific bet
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict2 = snapshot.value as? [String: AnyHashable] else {
return
}
let userNameOfOtherPlayer = dict2["Username"] as? String
self.opposingUserNames = userNameOfOtherPlayer!
completion(self.opposingUserNames)
})
})
}) { (error) in
print(error.localizedDescription)
}
}
ok so with this updated code it cuts out the logic errors I had earlier, but now the app hangs on my waitAlertViewController. Not sure why. it does updated the bet in the firebase database so I know its working and running that code but its like never completing it all. sorry bibscy I see what you mean now
completion handlers are pretty powerful once you understand them better
//Notice that I made `result: String?` optional, it may or may not have a value.
func getOpoosingUserNames(_ username: String,_ index: Int, completion: #escaping (_ result: String?) -> Void ) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.userName = username
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
let betId = snapshot.key as String
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
if let show = dict["Show"] as? String {
let opposingUser = dict["OpposingUsername"] as? String
self.opposingUserNames.append(opposingUser!)
}
completion(opposingUserNames)
})
}) { (error) in
print(error.localizedDescription)
}
}
//update the be
func updateBet(_ index: Int, completion: #escaping (_ something: [String]?) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
let values = ["OpposingUsername": self.userName,"Show": "no"]
//store the values received from Firebase in let valueOfUpdate and pass this
// constant to your completion handler completion(valueOfUpdate) so that you can use this value in func
//tableView(_ tableView:_, didSelectRowAt indexPath:_)
let valueOfUpdate = self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
completion(valueOfUpdate)
}) { (error) in
print(error.localizedDescription)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
//take away that bitches money
self.takeAwayMoney(self.amountBets[indexPath.row])
//then delete that cell and do another pop up that says successful
// check if value is yes or no in the database
self.updateBet(indexPath.row, completion: {(result: String) in
guard let resultReceivedInupdateBet = result, else {
print("result of updateBet() is \(result)")
}
print("If you see this print, database was updated")
//calling this method with the indexPath.row clicked by the user
self.getOpoosingUserNames(self.userName, indexPath.row, completion: { (result: [String]) in
guard let resultReceivedIngetOpoosingUserNames = result{
print("result of getOpoosingUserNames is \(result)")
}
print("If you see this print, you received a value from db after calling getOpoosingUserNames and that value is in \(result) ")
//result is not nil, resultReceivedIngetOpoosingUserNames has the same value as result.
}//end of self.getOpoosingUserNames
self.checkForNo(indexPath.row)
self.amountBets.remove(at: indexPath.row)
self.tableView.reloadData()
print(self.opposingUserNames)
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames[indexPath.row], preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
self.opposingUserNames.remove(at: indexPath.row)
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}