How do I update UILabels synchronously with Firestore data? - swift

I'm currently building an iOS app that will synchronize account information from Firestore. I have the login/register process hooked up and working. However, I need help understanding how to update my logInOutBtn, fullNameTxt and emailTxt in my MenuVC automatically when an user logs in/out. Currently, it will update whenever I close then reopen the menu, but what should I use to automatically update it without having to close the menu? Thanks!
// MenuVC
override func viewDidAppear(_ animated: Bool) {
if let user = Auth.auth().currentUser , !user.isAnonymous {
// We are logged in
logInOutBtn.setTitle("Logout", for: .normal)
if UserService.userListener == nil {
UserService.getCurrentUser {
self.fullNameTxt.text = UserService.user.fullName
self.emailTxt.text = UserService.user.email
}
}
} else {
logInOutBtn.setTitle("Login", for: .normal)
self.fullNameTxt.text = "Sign in or create an account"
self.emailTxt.text = "to continue."
}
}
fileprivate func presentLoginController() {
let storyboard = UIStoryboard(name: Storyboard.LoginStoryboard, bundle: nil)
if #available(iOS 13.0, *) {
let controller = storyboard.instantiateViewController(identifier: StoryboardId.LoginVC)
present(controller, animated: true, completion: nil)
} else {
// Fallback on earlier versions
}
}
#IBAction func logInOutClicked(_ sender: Any) {
guard let user = Auth.auth().currentUser else { return }
if user.isAnonymous {
presentLoginController()
} else {
do {
try Auth.auth().signOut()
UserService.logoutUser()
Auth.auth().signInAnonymously { (result, error) in
if let error = error {
debugPrint(error)
Auth.auth().handleFireAuthError(error: error, vc: self)
}
self.presentLoginController()
}
} catch {
debugPrint(error)
Auth.auth().handleFireAuthError(error: error, vc: self)
}
}
}
// UserService
func getCurrentUser(completion: #escaping () -> ()) {
guard let authUser = auth.currentUser else { return }
let userRef = db.collection("users").document(authUser.uid)
userListener = userRef.addSnapshotListener({ (snap, error) in
if let error = error {
debugPrint(error.localizedDescription)
return
}
guard let data = snap?.data() else { return }
self.user = User.init(data: data)
completion()
})
// User Model
struct User {
var fullName: String
var address: String
var id: String
var email: String
var stripeId: String
init(fullName: String = "",
address: String = "",
id: String = "",
email: String = "",
stripeId: String = "") {
self.fullName = fullName
self.address = address
self.id = id
self.email = email
self.stripeId = stripeId
}
init(data: [String : Any]) {
fullName = data["fullName"] as? String ?? ""
address = data["address"] as? String ?? ""
id = data["id"] as? String ?? ""
email = data["email"] as? String ?? ""
stripeId = data["stripeId"] as? String ?? ""
}
static func modelToData(user: User) -> [String : Any] {
let data : [String : Any] = [
"fullName" : user.fullName,
"address" : user.address,
"id" : user.id,
"email" : user.email,
"stripeId" : user.stripeId
]
return data
}
}
// My app menu

The signout process is pretty straightforward and is marked as throws so if it fails, it will generate an error that can be handled by a catch. It is not asynchronous so it won't have (or need) a closure.
So simply stated
func signOut() {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
print("successful signout")
self.logInOutBtn.setTitle("Log In", for: .normal)
self.fullNameTxt.text = ""
self.emailTxt.text = ""
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
//present the error to the user/handle the error
}
}
The signIn function is asynchronous with a closure so when the user signs in successfully, the code in the closure will fire and that's the perfect place to update the UI.
Auth.auth().signIn(withEmail: email, password: password) { [weak self] authResult, error in
guard let strongSelf = self else { return }
// update the UI here.
}
You can also just monitor the authState with an observer and have it react to users logging in/out
self.authListener = Auth.auth()?.addAuthStateDidChangeListener { auth, user in
if let theUser = user {
print("User logged in \(theUser)") // User is signed in.
self.dismissViewControllerAnimated(true, completion: nil)
} else {
print("Need to login.") // No user is signed in.
//present login view controller
}
}
If you no longer want to observe the auth state, you can remove it with
Auth.auth()?.removeAuthStateDidChangeListener(self.authListener)

Related

Bug while error handling Firebase Authentication with swiftUI

This is my AuthViewModel:
#Published var userSession: User?
#Published var currentUser: AppUser?
#Published var signupError: Error?
#Published var loginError: Error?
static let shared = AuthViewModel()
init() {
self.userSession = Auth.auth().currentUser
fetchUser()
}
func fetchUser() {
guard let uid = userSession?.uid else { return }
USER_COLLECTION.document(uid).getDocument { snapshot, _ in
guard let user = try? snapshot?.data(as: AppUser.self) else { return }
self.currentUser = user
}
}
func registerUser(withEmail email: String, password: String) {
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
self.signupError = error
print("error")
return
}
guard let user = result?.user else { return }
let data: [String: Any] = ["uid": user.uid, "email": user.email ?? ""]
USER_COLLECTION.document(user.uid).setData(data) { err in
self.userSession = user
self.fetchUser()
}
}
}
func login(withEmail email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
self.loginError = error
return
}
guard let user = result?.user else { return }
self.userSession = user
self.fetchUser()
}
}
I have 2 published variables which are updated every time there is an error in their respective functions. However, when I click the 'sign up' button the first time with invalid credentials, I don't get an alert. I have to click it the second time to see the alert. Attached below is the code from the sign up view SwiftUI button with action and label. The same applies to the login view.
Button {
authViewModel.registerUser(withEmail: email, password: password)
print("called")
if let error = authViewModel.signupError {
alertTitle = "Error signing up!"
alertMessage = error.localizedDescription
alertShowing = true
print(alertShowing)
}
print(alertShowing)
} label: {
CustomAuthButton(text: "Sign Up")
}
Below is the code attached to the sign up view (navigation view):
.alert(alertTitle, isPresented: $alertShowing) {
Button("OK") {
authViewModel.signupError = nil
}
} message: {
Text(alertMessage)
}
Attached below is the code for registering a user. I'll put the result of those print statements below.
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
DispatchQueue.main.async {
self.signupError = error
print("error")
return
}
}
called
false
2022-07-06 12:59:54.424510+0530 InfoMax[62528:2751506] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
error
As you can see, error is being printed late. When I tap the button the second time, it is printed before, so I get the alert.

How can I call a function from a Swift file and use it in a ViewController?

I have a Swift file that gets details about the user that is currently logged in/signed up named CognitoUserPoolController.swift
import Foundation
import AWSCognitoIdentityProvider
class CognitoUserPoolController {
let userPoolRegion: AWSRegionType = "Private Info"
let userPoolID = "Private Info"
let appClientID = "Private Info"
let appClientSecret = "Private Info"
var userPool:AWSCognitoIdentityUserPool?
var currentUser:AWSCognitoIdentityUser? {
get {
return userPool?.currentUser()
}
}
static let sharedInstance: CognitoUserPoolController = CognitoUserPoolController()
private init() {
let serviceConfiguration = AWSServiceConfiguration(region: userPoolRegion, credentialsProvider: nil)
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: appClientID,
clientSecret: appClientSecret,
poolId: userPoolID)
AWSCognitoIdentityUserPool.register(with: serviceConfiguration,
userPoolConfiguration: poolConfiguration,
forKey:"AWSChat")
userPool = AWSCognitoIdentityUserPool(forKey: "AWSChat")
AWSDDLog.sharedInstance.logLevel = .verbose
}
func login(username: String, password:String, completion:#escaping (Error?)->Void) {
let user = self.userPool?.getUser(username)
let task = user?.getSession(username, password: password, validationData:nil)
task?.continueWith(block: { (task: AWSTask<AWSCognitoIdentityUserSession>) -> Any? in
if let error = task.error {
completion(error)
return nil
}
completion(nil)
return nil
})
}
func signup(username: String, password:String, emailAddress:String, completion:#escaping (Error?, AWSCognitoIdentityUser?)->Void) {
var attributes = [AWSCognitoIdentityUserAttributeType]()
let emailAttribute = AWSCognitoIdentityUserAttributeType(name: "email", value: emailAddress)
attributes.append(emailAttribute)
print(emailAttribute.value!)
let task = self.userPool?.signUp(username, password: password, userAttributes: attributes, validationData: nil)
task?.continueWith(block: {(task: AWSTask<AWSCognitoIdentityUserPoolSignUpResponse>) -> Any? in
if let error = task.error {
completion(error, nil)
return nil
}
guard let result = task.result else {
let error = NSError(domain: "Private Info",
code: 100,
userInfo: ["__type":"Unknown Error", "message":"Cognito user pool error."])
completion(error, nil)
return nil
}
completion(nil, result.user)
return nil
})
}
func confirmSignup(user: AWSCognitoIdentityUser, confirmationCode:String, completion:#escaping (Error?)->Void) {
let task = user.confirmSignUp(confirmationCode)
task.continueWith { (task: AWSTask<AWSCognitoIdentityUserConfirmSignUpResponse>) -> Any? in
if let error = task.error {
completion(error)
return nil
}
completion(nil)
return nil
}
}
func resendConfirmationCode(user: AWSCognitoIdentityUser, completion:#escaping (Error?)->Void) {
let task = user.resendConfirmationCode()
task.continueWith { (task: AWSTask<AWSCognitoIdentityUserResendConfirmationCodeResponse>) -> Any? in
if let error = task.error {
completion(error)
return nil
}
completion(nil)
return nil
}
}
func getUserDetails(user: AWSCognitoIdentityUser, completion:#escaping (Error?, AWSCognitoIdentityUserGetDetailsResponse?)->Void) {
let task = user.getDetails()
task.continueWith(block: { (task: AWSTask<AWSCognitoIdentityUserGetDetailsResponse>) -> Any? in
if let error = task.error {
completion(error, nil)
return nil
}
guard let result = task.result else {
let error = NSError(domain: "Private Info",
code: 100,
userInfo: ["__type":"Unknown Error", "message":"Cognito user pool error."])
completion(error, nil)
return nil
}
completion(nil, result)
return nil
})
}
}
After a user successfully signs up they are presented with HomeViewController. In HomeViewController I try to print an attribute email value like this but it does not work
import UIKit
import AWSCognitoIdentityProvider
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let userpoolController = CognitoUserPoolController.sharedInstance
userpoolController.getUserDetails(user: userpoolController.currentUser!) { (error: Error?, details:AWSCognitoIdentityUserGetDetailsResponse?) in
view.backgroundColor = .green // This line of code works, but below this line it does not.
if let loggedInUserAttributes = details?.userAttributes {
self.view.backgroundColor = .systemPink
for attribute in loggedInUserAttributes {
if attribute.name?.compare("email") == .orderedSame {
print ("Email address of logged-in user is \(attribute.value!)")
}
}
}
}
}
}
The background color successfully changes to green but does not change to pink (That was to see if the code was working.) Inside of the if let statement is where the code is not working and there are not any errors. How can I properly fix this?

"Flag" / Report user functionality to work — Swift / Xcode / Firebase

I'm trying to allow a user to report a user (in a Tinder-like app). To report, this button takes the user to a new VC to elaborate the issue as an email.
What I'm missing:
How can I add the reporter's and reportee's Firebase unique ID to the email (or whatever form of communication)? (So then I can investigate and take action as needed)
Here's what I have:
The code to properly send an email...
func configureMailController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setToRecipients(["RadiusAppHelp#gmail.com"])
mailComposerVC.setSubject("Reporting user")
mailComposerVC.setMessageBody("Please include as much detail as possible:", isHTML: false)
return mailComposerVC
}
func showMailError() {
let sendMailErrorAlert = showAlert(withTitle: "Could not send message", message: "Please try again")
let dismiss = UIAlertAction(title: "Okay", style: .default, handler: nil)
// sendMailErrorAlert.addAction(dismiss)
// self.present(sendMailErrorAlert, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
The code to pull the other user's ID in the swipe view VC...
var otherUsersId = ""
var currentlyViewedUserId: String?
firebaseServer.fetchUsers {[weak self] (usersDict) in
self?.usersDict = usersDict
let fetchedUsers = Array(usersDict.values)
self?.filterBlockedUsers(from: fetchedUsers)
self?.loadFirstUser()
self?.cardView.reloadData()
}
func loadFirstUser() {
if users.count > 0 {
let imageView = UIImageView()
let storage = Storage.storage()
let storageRef = storage.reference(withPath:
"\(users[0].userId!)/photos/\(0)")
currentlyViewedUserId = users[0].userId
PhotoUploader.downloadImageUrl(from:
storageRef) { (url) in
guard let url = url else { return }
imageView.downloaded(from: url,
contentMode: .scaleAspectFill)
}
nameLbl.text = users[0].firstName
setupDetailsFor(user: users[0])
infoCollectionView.reloadData()
}
}
As well as the code to block a user (but blocking / reporting functions work independently).
Any help is greatly appreciated!
Okay, I found at least a temporary fix...
(Not using Firestore however, which I'll eventually need to implement — https://www.youtube.com/watch?v=Ofux_4c94FI)
In FirebaseFunctions.swift...
// Report Someone
func reportSomeone(with userId: String, completion:
#escaping (Error?) -> Void) {
let usersRef = db.child("users")
if let uid = Auth.auth().currentUser?.uid {
usersRef.child("\(uid)/report/\ .
(userId)").setValue(true) { (error, dbref) in
completion(error)
}
}
}
// Set Preferences for Reporting
func reportPreferences(with userId: String,
completion: #escaping (Error?) -> Void) {
let usersRef = db.child("users")
if let uid = Auth.auth().currentUser?.uid {
usersRef.child("\(uid)/preferences/\ .
(userId)").setValue(true) { (error, dbref) in
completion(error)
}
}
}
In User.swift...
var report: [String: Bool]? = [:]
func makeDictionary() -> [String: Any] {
print("")
return [
"report": report ?? [:]
]
static func makeObjectFrom(_ dictionary: [String:
Any]) -> LocalUser {
let report = dictionary["report"] as?
[String:Bool] ?? [:]
}
let localUser = LocalUser(report: report)
In the View Controller...
import Firebase
var currentUserId = Auth.auth().currentUser?.uid
var otherUsersId = ""
// Report Button
var isCurrentUserReported = false
var isOtherUserReported = false
var currentlyViewedUserId: String?
// FILTER REPORTED USERS
func filterReportUsers(from users: [LocalUser]) {
var notReportUsers = users
var notReportUsersDict = self.usersDict
var reportUsers = newUser.report ?? [:]
if let currentUserId =
Auth.auth().currentUser?.uid {
reportUsers[currentUserId] = true
}
for (userId, report) in reportUsers ?? [:] {
print("UserId...", userId)
print("UserHere...", usersDict[userId])
if let user = usersDict[userId] {
notReportUsersDict.removeValue(forKey:
userId)
}
}
let notReport = Array(notReportUsersDict.values)
self.users = notReport
}
This isn't perfect, but it works!

Save User's name in Firebase - Swift

I have three text field in my registration form. The e-mail id and password of the user are used by the Firebase SignUp method to create a new user. But I also want to save the user's name according to what they input.
My current code is;
#IBAction func registerPressed(_ sender: Any) {
SVProgressHUD.show(withStatus: "Setting you up")
dismissUIElements(value: false)
let currentUserName = userName.text
if currentUserName?.isEmpty == false {
FIRAuth.auth()?.createUser(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (user, error) in
if error != nil {
print(error!)
SVProgressHUD.dismiss()
self.dismissUIElements(value: true)
} else {
print("Registration Successful!")
SVProgressHUD.dismiss()
self.dismissUIElements(value: true)
self.performSegue(withIdentifier: "goToSelectionFromRegister", sender: self)
}
})
}
else {
SVProgressHUD.dismiss()
SVProgressHUD.showError(withStatus: "Please enter your name!")
SVProgressHUD.dismiss(withDelay: 1)
self.dismissUIElements(value: true)
}
}
You need a function that will register the user and then create the child for that user in Firebase.
let databaseRef=//your path to users.
func registerUser(userUsername userName:String, userEmail email:String, userPassword password: String, userCreationComplete: #escaping (_ status: Bool, _ error: Error?) -> ()) {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
guard let user = user else {
userCreationComplete(false, error)
return
}
let userData = ["userName": userName.text] as [String : Any]
ref.child(user.uid).updateChildValues(userData)
userCreationComplete(true, nil)
}
}
Then you call the function from within registerPressed() and you pass it the textfield values but make sure that none of them is empty.
You will create a new data table that stores that info. It won't be done in the create user function.
// create a reference to the DB
ref = Database.database().reference(fromURL: "https://your-project-name.firebaseio.com/")
//variables to store data
var myID : String?
var myName : String?
var myNumber : String?
var myEmail : String?
// creating the save user function
func saveUser(_ completion: #escaping(_ error: Error?) -> Void) {
if PageDataSource.sharedInstance.crudIsAvailable == true {
let usersRef = ref.child("users")
let myUserRef = usersRef.child(id)
myUserRef.updateChildValues(["User ID": id,
"Name": myName,
"Email": myEmail,
"Phone": .myNumber], withCompletionBlock: { (error, ref) in
if error != nil {
completion(error!)
} else {
completion(nil)
}
})
} else {
completion(NSError(domain: "Unavailable", code: 0, userInfo: nil))
}
}
// call the method like this to perform the save
func storeUser(completion: #escaping(_ completed: Bool, _ error: NSError?)-> Void) {
if let user = Auth.auth().currentUser {
myID = user.uid
myName = user.displayName
myEmail = user.email
// etc.,
completion(true,nil)
} else {
completion(false,NSError(domain: "No Current User", code: 1, userInfo: nil))
}
}

UserInfo={NSLocalizedDescription=The email address is already in use by another account., error_name=ERROR_EMAIL_ALREADY_IN_USE}

Hey guys actually i am trying two things here:- trying to create a new account and trying to open a screen like which appears after login but it is showing "email already exist error".
#IBAction func CreateAcccountButton(_ sender: AnyObject) {
guard let eventInterest = textBox.text,let email = EmailTestfield.text, let password = PasswordTestfield.text, let name = UsernameTestfield.text else {
print("Form is not valid")
return
}
Auth.auth().createUser(withEmail: email, password: password, completion: { (user, error) in
if let error = error {
print(error)
return
}
guard let uid = user?.uid else {
return
}
//successfully authenticated user
let imageName = UUID().uuidString
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).png")
if let uploadData = UIImagePNGRepresentation(self.Profilepicture.image!) {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if let error = error {
print(error)
return
}
print (metadata)
// let downloadURL = metadata?.downloadURL()
// print("URL ", downloadURL)
if let Profilepictureurl = metadata?.downloadURL()?.absoluteString {
let values = ["name": name, "email": email,"EventInterest":eventInterest,"Password":password,"Profilepictureurl": Profilepictureurl ]
let user = User(dictionary: values as [String : AnyObject])
let customViewController = MessagesController()
customViewController.setupNavBarWithUser(user)
customViewController.fetchUserAndSetupNavBarTitle()
// customViewController.navigationItem.title = values["name"] as? String
self.dismiss(animated: true, completion: nil)
self.registeruserintoDb(uid,values: values as [String : AnyObject])
}
})
}
}
)
}
fileprivate func registeruserintoDb(_ uid: String, values: [String: AnyObject]) {
let ref = Database.database().reference()
let usersReference = ref.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err!)
return
}
})
}
It's exactly what the error says, you already have a user with that email. Instead, use the auth.signIn method and check for currently signed in users.