Firebase sign in not working after I sign out - swift

So the first sign in into the Swift UI app works fine. I sign in and the view changes to the home view from the sign in view. However, after I log out, the sign in doesn't cause my view to change, so I assume it doesn't work.
Here is my AuthViewModel where I handle the sign in and sign out.
import FirebaseAuth
class AuthViewModel: ObservableObject {
static let shared = AuthViewModel()
private(set) var authSubscription: AuthStateDidChangeListenerHandle?
#Published var currentUser: User?
#Published var error: Error?
init() {
authSubscription = Auth.auth().addStateDidChangeListener { [weak self] (auth, user) in
self?.currentUser = user
}
}
func signIn(email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) { [weak self] (authResult, error) in
if let error = error {
self?.error = error
} else {
self?.error = nil
}
}
}
func signUp(email: String, password: String) {
Auth.auth().createUser(withEmail: email, password: password) { [weak self] (authResult, error) in
if let error = error {
self?.error = error
} else {
self?.error = nil
}
}
}
func signOut() {
do {
try Auth.auth().signOut()
} catch let error {
self.error = error
}
}
}
Also here is how I am switching from my sign up view to the home view
struct ContentView: View {
#State var screenOn = 0
#EnvironmentObject var authViewModel: AuthViewModel
var body: some View {
if let user = authViewModel.currentUser {
if screenOn == 0 {
someView(screenOn: $screenOn)
.onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
.onOpenURL { url in
let link = url.absoluteString
}
} else if screenOn == 1 {
someView(screenOn: $screenOn)
.onAppear {
authViewModel.signOut()
}
} else if screenOn == 3 {
someView(screenOn: $screenOn)
} else if screenOn == 4 {
someView(screenOn: $screenOn)
}
} else {
Loginpage()
}
}
}

Related

Can someone explain what "extra trailing closure passed in call" means?

I'm receiving the error "Extra trailing closure passed in call" on the authViewModel.fetchUser() function. From what I've gathered researching online this means that fetchuser can't have the trailing closure (the brackets), I am confused about what in my fetchuser function says that it cannot have the {} after the call. Or maybe I'm not understanding the error at all. Thank you in advance!
FeedCellViewModel
import Foundation
class FeedCellViewModel: ObservableObject {
#Published var posts = [Post]()
let service = PostService()
let authViewModel = AuthViewModel()
init() {
fetchPosts()
}
func fetchPosts() {
service.fetchPosts { posts in
self.posts = posts
for i in 0 ..< posts.count {
let uid = posts[i].uid
self.authViewModel.fetchUser() { user in
self.posts[i].user = user
}
}
}
}
}
AuthViewModel
import SwiftUI
import FirebaseAuth
import FirebaseCore
import FirebaseStorage
import FirebaseFirestore
import FirebaseFirestoreSwift
class AuthViewModel: NSObject, ObservableObject {
#Published var userSession: FirebaseAuth.User?
#Published var currentUser: User?
#Published var selectedImage: UIImage?
#Published var didAuthenticateUser = false
private var tempUserSession: FirebaseAuth.User?
private let service = UserService()
static let shared = AuthViewModel()
override init() {
super.init()
userSession = Auth.auth().currentUser
fetchUser()
}
func login(withEmail email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to sign in with error \(error.localizedDescription)")
return
}
self.userSession = result?.user
self.fetchUser()
}
}
func register(withEmail email: String, password: String, fullname: String) {
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to register with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.tempUserSession = user
let data = ["email": email,
"fullname": fullname,
"uid": user.uid]
COLLECTION_USERS
.document(user.uid)
.setData(data) { _ in
self.didAuthenticateUser = true
}
self.uploadProfileImage(self.selectedImage)
self.fetchUser()
}
}
func signOut() {
// sets user session to nil so we show login view
self.userSession = nil
// signs user out on server
try? Auth.auth().signOut()
}
func uploadProfileImage(_ image: UIImage?) {
guard let uid = tempUserSession?.uid else { return }
ImageUploader.uploadImage(image: image) { profileImageUrl in
Firestore.firestore().collection("users")
.document(uid)
.updateData(["profileImageUrl": profileImageUrl]) { _ in
self.userSession = self.tempUserSession
}
}
}
func fetchUser() {
guard let uid = userSession?.uid else { return }
COLLECTION_USERS.document(uid).getDocument { snapshot, _ in
guard let user = try? snapshot?.data(as: User.self) else { return }
self.currentUser = user
}
}
}
You're seeing this error because your fetchUser() function doesn't take a closure parameter (or any parameters for that matter).
A trailing closure is just a nicer way of passing a closure as a parameter, given it's the last parameter to a method.
Try running this example in a playground to get a feel for this:
func hello(closure: () -> Void) {
print("calling closure")
closure()
print("finished")
}
// these are the same
hello(closure: { print("hello!!") })
hello { print("hello!!") }
If you want to provide the user in a closure to the caller, return the user as a parameter to the closure in addition to setting the currentUser.
func fetchUser(finishedFetching: #escaping (User) -> Void) {
guard let uid = userSession?.uid else { return }
COLLECTION_USERS.document(uid).getDocument { snapshot, _ in
guard let user = try? snapshot?.data(as: User.self) else { return }
self.currentUser = user
finishedFetching(user)
}
}
Read more about closures in the Swift language guide.
They are essentially unnamed functions that you can store and pass around.
You'll learn there why I marked our closure as #escaping.

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.

Why is my swift code executing out of order? Firebase authentication

I am calling Authenticator.loginUser() with the expectation that the method will log in a user then call getCurrentUser(). Based on printed output, getCurrentUser() is executing first. Is there a way to force it to execute in order?
class Authenticator: ObservableObject {
#Published var currentUser: UserProfile = UserProfile()
#Published var user: String = ""
#Published var documentId: String = ""
func loginUser (email: String, password: String, viewModel: UsersViewModel) {
FirebaseAuth.Auth.auth().signIn(withEmail: email, password: password, completion: { result, error in
guard error == nil else {
print ("error: \(error!)")
return
}
print ("user signed in")
})
self.user = self.getCurrentUser(viewModel: viewModel)
}
func getCurrentUser(viewModel: UsersViewModel) -> String {
guard let userID = Auth.auth().currentUser?.uid else {
return ""
}
viewModel.users.forEach { i in
if (i.userId == userID) {
currentUser = i
}
}
documentId = currentUser.documentId!
print("auth.documentId \(documentId)")
return userID
}
}
FirebaseAuth.Auth.auth().signIn is asynchronous - it runs in the background and not on the main thread. This means that signIn will be called some time in the future.
A solution is to put the call to getCurrentUser in the completion block:
func loginUser(email: String, password: String, viewModel: UsersViewModel) {
FirebaseAuth.Auth.auth().signIn(withEmail: email, password: password, completion: { result, error in
guard error == nil else {
print("error: \(error!)")
return
}
print("user signed in")
self.user = self.getCurrentUser(viewModel: viewModel) // move here
})
}

How to Log-in with Firebase in Swift UI?

I can create user through my code and get in to the next view but I can not log in with that mail address and password I just created. On the other hand, when I create a user in Firebase authentication page without programmatically, I can log in with that mail address and password. I don't know why this is happening.
My login button's codes are as following...
Button(action: {
self.verify()
}) {
Text("Log In")
}
func verify(){
if self.mail != "" && self.pass != ""{
Auth.auth().signIn(withEmail: self.mail, password: self.pass) { (res, err) in
if err != nil{
self.error = err!.localizedDescription
self.alert.toggle()
return
}
print("success")
UserDefaults.standard.set(true, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("status"), object: nil)
}
}
else{
self.error = "Please fill all the contents properly"
self.alert.toggle()
}
}
My signUp button's codes are as following...
Button(action: {
self.register()
}) {
Text("Sign Up")
}
func register(){
if self.mail != ""{
if self.pass == self.repass{
Auth.auth().createUser(withEmail: self.mail, password: self.pass) { (res, err) in
if err != nil{
self.error = err!.localizedDescription
self.alert.toggle()
return
}
print("success")
UserDefaults.standard.set(true, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("status"), object: nil)
}
}
else{
self.error = "Password mismatch"
self.alert.toggle()
}
}
else{
self.error = "Please fill all the contents properly"
self.alert.toggle()
}
}
To Sign In in Firebase I recommend some updates in your code:
Use completion to your Sign In and Sign Up functions, this way you know for sure that Firebase is answer you.
Handle listener AuthStateDidChangeListenerHandle. This listener gets called whenever the user's sign-in state changes.
API Authentification:
class AuthService {
static func signInUser(email: String, password: String, onSucces: #escaping() -> Void, onError: #escaping (_ errorMessage : String) -> Void ) {
Auth.auth().signIn(withEmail: email, password: password) { (authData, error) in
if (error != nil) {
print(error!.localizedDescription)
onError(error!.localizedDescription)
return
}
//Sign In Code
onSuccess()
}
static func signUpUser(email: String, password: String, username: String, onSucces: #escaping() -> Void, onError: #escaping (_ errorMessage : String) -> Void ) {
Auth.auth().createUser(withEmail: email, password: password) { (authData, error) in
if (error != nil) {
print(error!.localizedDescription)
onError(error!.localizedDescription)
return
}
//Sign Up Code
onSuccess()
}
}
}
in your View or ViewModel:
func verify() {
if self.mail != "" && self.pass != ""{
AuthService.signUpUser(email: email, password: password, username: username, onSucces: {
print("Sign In!")
}) { (error) in {
print("Error \(error)")
})
}
}
Class Session, to handle Auth Listener
class SessionAuth : ObservableObject {
#Published var isLoggedIn = true
var handle : AuthStateDidChangeListenerHandle?
func listenAuthentificationState() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
//You are log in
// Your login process code
self.isLoggedIn = true
}
else {
//You are not
self.isLoggedIn = false
}
}
}
Your main View:
struct ContentView: View {
#ObservedObject var session : SessionAuth //or EnvironmentObject
func listen() {
session.listenAuthentificationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainLoggedView()
}
else {
SignInView()
}
}.onAppear(perform: listen)
}
}
In if else()
you need to "return ();"
and,
you should create login activity in programa manner.
FOR bETTER ResulT.

How to use Firebase Auth + Combine to Sign-In and prevent Sign-In

So I've been following Log-In SwiftUI tutorials for Firebase and it's doing what it's supposed to for the most part: An error pops up when one or more of the input fields are left blank.
The problem occurs however, when I fill in the username + password fields with random gibberish. The view changes rather than popping up an error saying that the username is invalid (which I see in my console).
I've done some research and found that the problem might be due to the asynchronous behavior of Firebase and I haven't necessarily connected the error toggle to the sign-In result. But as a noob, I don't know how to implement the trailing closure in my code, and unsure where to go from here.
What change do I need to make exactly to ensure that upon failure of signing in with firebase, the view does not change and error pops up?
Here's my Sign-In function:
func signIn(){
error = false
session.signIn(email: user_account, password: password){
(result, error) in
if let errornew = error {
self.inputerror = errornew.localizedDescription
print("\(String(describing:error))")
self.error = true
self.alert.toggle()
} else {
self.user_account = ""
self.password = ""
}
}
}
Parent View:
struct ContentView: View {
#EnvironmentObject var session:SessionStore
#State var setUp = false
func getUser(){
session.listen()
}
var body: some View {
Group{
if(session.session != nil){
Text("App Home Page")
Text("Welcome")
Text("Email: \(session.session?.email ?? "")")
} else {
OpeningView() // Sign-In function is in a child view under OpeningView()
}
}.onAppear(perform: self.getUser)
}
Session Class:
class SessionStore: ObservableObject{
#Published var isSetUp:Bool?
var didChange = PassthroughSubject<SessionStore, Never>()
var session: User? {didSet {self.didChange.send(self)}}
var handle: AuthStateDidChangeListenerHandle?
let user = Auth.auth().currentUser
//checks to see whether or not we have a user
func listen(){
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
//if we have a user, create a new user model
print("Got the user: \(user)")
self.session = User(uid: user.uid, email: user.email!)
} else {
//if not, then session is nil
self.session = nil
}
})
}
func signUp(email: String, password: String, handler: #escaping AuthDataResultCallback){
Auth.auth().createUser(withEmail: email, password: password, completion: handler)
}
func signIn(email: String, password: String, handler: #escaping AuthDataResultCallback){
Auth.auth().signIn(withEmail: email, password: password, completion: handler)
}
func login(withEmail email: String, password: String, _ callback: ((Error?) ->())? = nil){
Auth.auth().signIn(withEmail: email, password: password){(user, error) in
if let e = error{
callback?(e)
return
}
callback?(nil)
print("Login Successful")
}
}
func signOut(){
do{
try Auth.auth().signOut()
self.session = nil
} catch {
print("Error Signing Out.")
}
}
func unbind(){
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
deinit{
unbind()
}
}
struct User {
var uid : String
var email : String?
init(uid: String, email: String?){
self.uid = uid
self.email = email
}
}
Your View needs something like this on top to check whether you are logged in or not:
var body: some View {
ZStack {
if Auth.auth().currentUser != nil {
// Your View when you are logged in.
else {
SignInView()
} }