How to use FIRApp.createUserWithEmail in specific database reference? - swift

Iv been converting my post Firebase 2 codebase to Firebase 3 and having some troubles.
So basically Im trying to figure out how I create a new user at a specific location on my Firebase DB.
Goal - I want to save all new users # var _USER_REF = FIRDatabaseReference().child("\(BASE_URL)/users")
Here is the code so far.
class DataService {
static let dataService = DataService()
let BASE_URL = "https://project-1321.firebaseio.com"
var _BASE_REF = FIRDatabaseReference().child(BASE_URL)
var _USER_REF = FIRDatabaseReference().child("\(BASE_URL)/users")
var _NEWS_REF = FIRDatabaseReference().child("\(BASE_URL)/news")
var _MARKET_STATS = FIRDatabaseReference().child("\(BASE_URL)/market")
var CURRENT_USER_REF: FIRDatabaseReference {
let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
let currentUser = FIRDatabaseReference().child("\(_BASE_REF)").child("users").child(userID)
//let currentUser = Firebase(url: "\(BASE_REF)").childByAppendingPath("users").childByAppendingPath(userID)
return currentUser
}
func createNewAccount(uid: String, user: Dictionary<String, String>) {
_USER_REF.child(uid).setValue(user)
}
}
View Controller
#IBAction func registerAccount(sender: AnyObject) {
guard let email = self.emailRegField.text where !self.emailRegField.text!.isEmpty else {
return
}
guard let username = self.usernameRegField.text where !self.usernameRegField.text!.isEmpty else {
return
}
guard let password = self.passwordRegField.text where !self.passwordRegField.text!.isEmpty else {
return
}
FIRAuth.auth()?.createUserWithEmail(email, password: password) {
(user, error) in
if error != nil {
print(error)
self.signUpErrorAlert("Alert", message: "There was a problem signing up!")
} else {
let user = ["provider": user?.providerID, "email": email, "username": username]
DataService.createNewAccount(user) // Doesnt Work
}
//Store UID in NSDefaults so if user reopen app they automatically log in if UID exsisits.
NSUserDefaults.standardUserDefaults().setValue(result ["uid"], forKey: "uid")
// Segue New User
self.performSegueWithIdentifier("newUserSegue", sender: nil)
}
// Loggin in a User who already has UID Saved to NSDefaults
When a user log's in or Registers I plan to save their "UID" to NSDefaults.
Then check like so :
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if NSUserDefaults.standardUserDefaults().valueForKey("uid") != nil {
self.performSegueWithIdentifier("newUserSegue", sender: nil)
} else {
print("User is not registered or has their UID saved to NSDefaults")
}
}
Is this a safe method?

#brkr In response to your comment above, you can still use the UIDs in order to add unique users to your Firebase database.
For example, here is your users ref (in the DataService Class):
let REF_USERS = FIRDatabase.database().referenceFromURL("\(URL_BASE)/users")
Now, create a user in Firebase 3.0:
FIRAuth.auth()!.createUserWithEmail(email, password: pwd, completion: { authData, error in
if error == nil {
// Log user in
FIRAuth.auth()?.signInWithEmail(email, password: pwd) { authData, error in
// Save user information to Firebase data
let user = // your user Dictionary
DataService.createNewAccount(authData!.uid, user: user)
}
} else {
// Handle login error here
}
})
The code for the create account method:
func createNewAccount(uid: String, user: Dictionary<String, String>) {
REF_USERS.child(uid).setValue(user)
}

I dont think it is necessary with new Firebase, look in the Dashboard, all your users should be under "Auth" tab,
Also this line doesnt make any sense in new Firebase, the URL you are querying is in the .plist you downloaded.
let BASE_URL = "https://project-1321.firebaseio.com" //remove this line
and use something like this
let firebaseRef = FIRDatabase.database().reference()
let newsRef = firebaseRef.child("news")
you can find many useful informations here https://firebase.google.com/docs/auth/ios/password-auth#sign_in_a_user_with_an_email_address_and_password

Related

Swiftui - Fetch data after login

I'm starting to learn SwiftUI and i'm looking for better solution to fetch user data after successful login.
For example, i need to see if the user has the account blocked or not after login
For this i have create struct in Users.swift
struct Users: Identifiable, Codable{
#DocumentID var id: String?
var Societe : String
var Nom : String
var Prenom : String
var HasBlocked : Bool
var SiteID : [String]
var Poste : String
var Email : String
}
and firebase class in Firebase.swift
class Firebase: ObservableObject {
#EnvironmentObject var viewRouter: ViewRouter
#Published var user : Users? = nil
private var db = Firestore.firestore()
func fetchData() {
db.collection("Users").document(Auth.auth().currentUser!.uid).addSnapshotListener{ (snapshot, error) in
guard let documents = snapshot?.data() else {
print("No documents")
return
}
let name = documents["Nom"] as? String ?? ""
let surname = documents["Prenom"] as? String ?? ""
let email = documents["Email"] as? String ?? ""
let societe = documents["Societe"] as? String ?? ""
let poste = documents["Poste"] as? String ?? ""
let siteId = documents["SiteID"] as? [String] ?? []
let hasBlocked = documents["HasBlocked"] as? Bool ?? true
if let user = self.user?.SiteID{
print(user)
}else{
print("no document2")
}
self.user = Users(Societe: societe, Nom: name, Prenom: surname, HasBlocked: hasBlocked, SiteID: siteId, Poste: poste, Email: email)
}
}
}
and in the SignInView after press login button, i call func signInUser
#ObservedObject private var firebase = Firebase()
func signInUser(userEmail: String, userPassword: String) {
signInProcessing = true
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
guard error == nil else {
signInProcessing = false
signInErrorMessage = error!.localizedDescription
return
}
switch authResult {
case .none:
print("Could not sign in user.")
signInProcessing = false
case .some(_):
print("User signed in")
// get data and check ??
signInProcessing = false
withAnimation {
viewRouter.currentPage = .homePage
}
}
}
}
But i don't know how to fetch data after login for check if the account has blocked and then pass the data to Homeview without re-fetch data to optimize call database.
thank for your help
Let me address this at a high level as your code seems fine but its's just wrapping your brain around the asynchronous calls.
I am pretty sure you have everything you need - here's the flow using pseudo code
authenticate { auth closure
read user document using auth.uid { snapshot closure
if snapshot has user blocked {
do not proceed to next view
} else { //user is not blocked
proceed to next view
}
}
}
Remember that Firebase data is only valid within the closure following the Firebase call and if you follow the above pseudo code, it does just that...
auth closure - has valid firebase auth information so you can get the uid
snapshot closure - has valid snapshot info so you can see if the user is blocked
the If statement will then determine the next thing to do - either don't show
the view if blocked or show it if not blocked
So borrowing from your code
func signInUser(userEmail: String, userPassword: String) {
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
db.collection("Users").document(Auth.auth().currentUser!.uid).getDocument {
either go to next view or not
Note that you should be using getDocument in this case to read the document once instead of adding a listener.

SwiftUI App can't get data from Firestore

I will start by explaining with words, then I will explain with code below. Here we go.
I have a struct 'User' that I use in my app. It has important data like username, email, and profileImageUrl. To access a list of users, I need to request a list of users from Firestore. But when I do this, it returns an empty array.
EXCEPT right after I log in. If I check the Users array before I log in, it's empty. If I check the users array after I log in, it's empty. But if I print it inside the .onAppear of the main content view that loads, it prints the true array of users.
I also notice that my program never thinks I'm the current user, even if I am. Each instance of User has a property isCurrentUser: Bool that is supposed to check if the user that is currently logged in is the same user in that instance. But they all appear as false.
It seems to me that the program thinks I'm not logged in, and will only let me pull data from the server if I'm logged in. Even though I changed my Firestore and Firebase Storage rules to allow me to read and write even if I'm not logged in.
Here are important snippets of code.
User struct
struct User : Identifiable {
let id : String
let username : String
let profileImageUrl : String
let email : String
let isCurrentUser: Bool
init (dictionary: [String: Any]) {
self.id = dictionary["uid"] as? String ?? ""
self.username = dictionary["username"] as? String ?? ""
self.profileImageUrl = dictionary["profileImageUrl"] as? String ?? ""
self.email = dictionary["email"] as? String ?? ""
self.isCurrentUser = Auth.auth().currentUser?.uid == self.id
}
}
notice the self.isCurrentUser code. I'm thinking maybe this code isn't working, or that self.id isn't working properly.
I also receive these two errors whenever I unsuccessfully fetch from the Users array.
2021-02-24 21:00:50.172361-0800 Council[38163:1564633] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
2021-02-24 21:00:52.168254-0800 Council[38163:1564630] [connection] nw_resolver_start_query_timer_block_invoke [C1] Query fired: did not receive all answers in time for firebaselogging-pa.googleapis.com:443
So please share any knowledge you have this problem is killing me i have to finish this app in time and now I am not able to work due to this bug
Here is my main ContentView that shows login screen if a user isn't logged in, but shows the homepage if a user is logged in. I called an instance of SearchViewModel() to test if I could print SearchViewModel().users. This is the view in which I can get the users array to print, but only right after I log in. If I attempt to print it on a subsequent page, it'll be the same empty array.
import SwiftUI
struct ContentView: View {
#State private var navBarHidden: Bool = true
#EnvironmentObject var viewModel : AuthViewModel
#ObservedObject var searchViewModel = SearchViewModel()
var body: some View {
Group{
if viewModel.userSession != nil{
NavigationView{
TabView{
FeedView(navBarHidden: $navBarHidden)
.navigationBarHidden(self.navBarHidden)
.tabItem{
Image(systemName: "house")
Text("For You")
}
Text("Random")
.tabItem{
Image(systemName: "questionmark.circle")
Text("Random")
}
SearchView()
.tabItem {
Image(systemName: "magnifyingglass")
Text("Search")
}
.navigationBarHidden(true)
}
.accentColor(.black)
.onAppear() {
searchViewModel.fetchUsers()
print(searchViewModel.users)
}
}
} else {
LoginView()
}
}
}
}
More code: Here is my User struct
import Foundation
import Firebase
struct User : Identifiable {
let id : String
let username : String
let profileImageUrl : String
let email : String
let isCurrentUser: Bool
init (dictionary: [String: Any]) {
self.id = dictionary["uid"] as? String ?? ""
self.username = dictionary["username"] as? String ?? ""
self.profileImageUrl = dictionary["profileImageUrl"] as? String ?? ""
self.email = dictionary["email"] as? String ?? ""
self.isCurrentUser = Auth.auth().currentUser?.uid == self.id
}
}
Here is the file for the ViewModel that fetches the users
import SwiftUI
import Firebase
class SearchViewModel: ObservableObject {
#Published var users = [User]()
init() {
fetchUsers()
print("usersFetched")
}
func fetchUsers() {
Firestore.firestore().collection("users").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {return}
documents.forEach{ document in
let user = User(dictionary: document.data())
self.users.append(user)
}
}
}
Here is the ViewModel that authenticates users, creates users, and logs them in
import SwiftUI
import Firebase
class AuthViewModel: ObservableObject {
#Published var userSession: FirebaseAuth.User?
#Published var isAuthenticating = false
#Published var error: Error?
#Published var user: User?
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("Failed to upload image. Error: \(error.localizedDescription)")
return
}
self.userSession = result?.user
print("U logged in")
}
}
func registerUser( email : String, password: String, username: String, profileImage: UIImage) {
guard let imageData = profileImage.jpegData(compressionQuality: 0.3) else {return}
let fileName = NSUUID().uuidString
let storageRef = Storage.storage().reference().child(fileName)
storageRef.putData(imageData, metadata: nil) { _, error in
if let error = error{
print("Failed to upload image. Error: \(error.localizedDescription)")
return
}
print("successful upload of photo")
storageRef.downloadURL { url , _ in
guard let profileImageUrl = url?.absoluteString else {return}
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("Failed to Register. Error: \(error.localizedDescription)")
return
}
guard let user = result?.user else {return}
let data = ["email": email,
"username": username.lowercased(),
"profileImageUrl": profileImageUrl,
"uid": user.uid]
Firestore.firestore().collection("users").document(user.uid).setData(data) { _ in
self.userSession = user
print("successfully uploaded user data")
}
print("Successful SIgnup")
}
}
}
}
func signOut() {
userSession = nil
try? Auth.auth().signOut()
}
func fetchUser() {
guard let uid = userSession?.uid else {return}
Firestore.firestore().collection("users").document(uid).getDocument { snapshot, _ in
guard let data = snapshot?.data() else {return}
let user = User(dictionary: data)
}
}
}
EDIT: New possibility for an issue. When the users array successfully prints, it does so in this format:
MyApp.User(id: "kugrbub", username: "user1", profileImageUrl: "https://theimageurl.com", email: "ceo#violent.jewelry", isCurrentUser: false),
When it prints, it's in parentheses, not braces. So is this a reason why it could be failing?
I believe I see what's going wrong. I think that you're misunderstanding what is happening with the asynchronous Firebase methods.
When you call a method like .getDocuments in Firebase, that method does not return instantaneously. Rather, it runs asynchronously and then calls your callback function that you provide when it's finished.
Why does this lead to the result that you're getting? In ContentView, you initialize SearchViewModel by calling SearchViewModel() to set up the property. In the init() of SearchViewModel, fetchUsers() is called, which populates the users array when it completes.
Then, once your login completes, ContentView switches over to your NavigationView/TabView, and onAppear is called. Inside, you call fetchUsers again, which starts running asynchronously. But, because you had already called fetchUsers in the SearchViewModel init, the array already has data in it (keep in mind that unless you explicitly sign out of Firebase, you'll still be signed in from the previous session).
So, how do you solve this?
Remember that all of those calls are asynchronous. Printing the arrays right after calling the fetchUsers method(s) will never get you the values you want *unless they had been populated before.
Restructure your app so that you can respond to the changes in the view model asynchronously. Because you have so much going on, there's not one definitive way to clean everything up. But, you can look into a couple of things:
look up onReceive which you can use to tell when a single publisher on your ObservableObject has changed.
When using Firebase, I like using Auth.auth().addStateDidChangeListener to respond to the auth state. That might help you start to split up your login logic.
Instead of printing after your fetchUsers call, print from inside the Firebase callback functions.
A code sample for you:
class SearchViewModel: ObservableObject {
#Published var users = [User]()
func fetchUsers() {
guard Auth.auth().currentUser?.uid != nil else {
assertionFailure("NOT SAFE TO GET USERS YET -- NOT LOGGED IN")
return
}
Firestore.firestore().collection("users").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {return}
print("Received docs:")
print(documents)
documents.forEach{ document in
let user = User(dictionary: document.data())
self.users.append(user)
}
print("USERS:")
print(self.users)
}
}
}
struct ShowUsersView: View {
#ObservedObject var searchViewModel = SearchViewModel()
var body: some View {
VStack {
ForEach(searchViewModel.users, id: \.id) { user in
Text(user.username)
}
}.onAppear {
print("Calling fetchUsers...")
searchViewModel.fetchUsers()
}
}
}

Realm objects returns nil after adding

I created an Person object when the User logs in:
let creds = SyncCredentials.jwt(accessToken)
SyncUser.logIn(with: creds, server: Constants.syncAuthURL, onCompletion: { [weak self](user, err) in
if let user = user {
self?.setDefaultRealmConfiguration(with: user)
let realm = try! Realm()
let identity = (user.identity)!
let person = Person()
person.id = identity
try! realm.write {
realm.add(person, update: true)
}
self?.performSegue(withIdentifier: "showProfile", sender: self)
}
})
The Person successfully created on the cloud server.
In the next viewcontroller i like to fetch the object based on the id:
let realm = try! Realm()
guard let uuid = SyncUser.current?.identity! else { return }
let person = realm.objects(Person.self).filter("id = %#", uuid).first
try! realm.write {
person?.name = "test"
}
The person is always nil I also tried the query the object with the primary key, but with no success.
The Person class looks like:
class Person : Object {
#objc dynamic var id = ""
#objc dynamic var created: Date = Date()
#objc dynamic var name = ""
#objc dynamic var email = ""
#objc dynamic var avatar : Data?
override static func primaryKey() -> String? {
return "id"
}
}
UPDATE
I created a new app with just one Viewcontroller and the Person class:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let creds = SyncCredentials.usernamePassword(username: "admin", password: "test")
SyncUser.logIn(with: creds, server: Constants.AUTH_URL, onCompletion: { (user, err) in
if let user = user {
var config = user.configuration(realmURL: Constants.REALM_URL)
config.objectTypes = [Person.self]
Realm.asyncOpen(configuration: config, callback: { (realm, error) in
guard let realm = realm else {return}
let objects = realm.objects(Person.self)
print(objects) // always empty why???
try! realm.write {
let p = Person()
p.id = "test"
realm.add(p)
}
print(objects) // one object
})
}
})
}
}
as with my other problem the person is successfully added to the cloud. but when I restart the app the objects are empty on the first query. Maybe I miss understanding something with the synched realms?
let results = realm.objects(Person.self)
let subscription = results.subscribe()
print(results)
resolve my problem

Cache implementation method

I want to save as cache as UserDefaults.
But,
This is signupButtonTapped:
#IBAction private func signUpButtonTapped(_ sender: Any) {
KRProgressHUD.show(withMessage: "loading")
Authentication.signUp(
name: nameTextField.text!, // "new"
email: emailTextField.text!, // "new"
password: passwordTextField.text!, // "new"
image: selectedImage,
onSuccess: {
KRProgressHUD.dismiss()
API.User.getCurrentUser { (user) in
CurrentUser.shared.synchronize(user: user)
}
self.presentViewController()
}) { (error) in
KRProgressHUD.showError(withMessage: "\(error!)")
}
}
this is CurrentUser class:
import Foundation
final class CurrentUser: User {
// Get
static let shared: CurrentUser = {
let user = CurrentUser()
let userDefaults = UserDefaults.standard
user.userID = userDefaults.string(forKey: userID_field)
user.name = userDefaults.string(forKey: name_field)
user.email = userDefaults.string(forKey: email_field)
return user
}()
// Save
func synchronize(user: user) {
print(user.name, user.userID, user.email) // "new", "new", "new"
// <<<< BREAKPOINT
let userDefaults = UserDefaults.standard
userDefaults.set(user.userID, forKey: userID_field)
userDefaults.set(user.name, forKey: name_field)
userDefaults.set(user.email, forKey: email_field)
print(userDefaults.string(forKey: name_field), userDefaults.string(forKey: userID_field), userDefaults.string(forKey: email_field)) // "new", "new", "new"
print(CurrentUser.shared.name, CurrentUser.shared.userID, CurrentUser.shared.email) // "old", "old", "old"
// <<<< BREAKPOINT
}
I want the last print(CurrentUser.shared.ooo) function to be "new", "new", "new".
However, the old value "old" is displayed instead.
What is wrong with my cache implementation?
Since you are using static instance shared that property will only hold the first value. You need to change your strategy to something else, because once your app is launched the computed property won't compute again and thus won't fetch data from userdefaults.
You see when you save your new data and then quit and launch your app again it should display the expected result, but since you are trying to get the data when your app is launched it will obviously display the previously stored one. A simple solution would be to not user shared instance for this, you could do something like following:
//Put the currentUser in User class
class User {
var userID = ""
var name = ""
var email = ""
class var current: User {
let user = User()
let defaults = UserDefaults.standard
user.email = defaults.string(forKey: email_field) ?? "" //this can be nil if no data
user.userID = defaults.string(forKey: userID_field) ?? ""
user.name = defaults.string(forKey: name_field) ?? ""
return user
}
func synchronize() {
let userDefaults = UserDefaults.standard
userDefaults.set(self.userID, forKey: userID_field)
userDefaults.set(self.name, forKey: name_field)
userDefaults.set(self.email, forKey: email_field)
print(userDefaults.string(forKey: name_field), userDefaults.string(forKey: userID_field), userDefaults.string(forKey: email_field))
print(User.current.name, User.current.userID, User.current.email)
}
}
Now you can perform your operations. Like so
let newUser = User()
newUser.userID = "123"
....
newUser.synchronize() //save
//When you need current user
User.current

Swift Firebase check if email is already in use

I want to be able to check if an email address is already been used (so if somebody put test1#test.com but another user already registered with that email account).
I have a simple test if it has NOT been used an image view shows a green arrow, if it HAS been used then it is red x
when I create the user I use the following code
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
if error == nil {
self.ref.child("userEmails").child((user?.uid)!).setValue(email)
FIRAuth.auth()!.signIn(withEmail: email,
password: password)
} else {
//registration failure
}
what I am trying to do to check is
func checkIfEmailExists(textField: UITextField) {
let ref = FIRDatabase.database().reference()
let email = firstContainerTextField.text ?? ""
ref.child("userEmails").queryEqual(toValue: email)
.observe(.value, with: { snapshot in
if (self.firstContainerTextField.text?.isEmpty)! {
self.firstContainerImage.image = UIImage.init(named: "emptyBlue.png")
} else if !(self.firstContainerTextField.text?.isEmpty)! && !snapshot.exists() {
self.firstContainerImage.image = UIImage.init(named: "redEx.png")
} else if snapshot.exists() {
self.firstContainerImage.image = UIImage.init(named: "greenCheck.png")
}
});
}
So far it does not work as I can see in my database that test1#test.com exists.
Can somebody tell me what I missed?
EDIT
I have updated my code. I am using hasChildren and I searched for similar questions and they seem to point this direction, but I still cannot get the result I am looking for
func checkIfEmailExists(textField: UITextField) {
let ref = FIRDatabase.database().reference()
let email = firstContainerTextField.text ?? ""
ref.child("userEmails").queryEqual(toValue: email)
.observe(.value, with: { snapshot in
if !snapshot.hasChildren() {
self.firstContainerImage.image = UIImage.init(named: "redEx.png")
} else {
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
let tmp = child.value as! String
if tmp == email {
self.firstContainerImage.image = UIImage.init(named: "greenCheck.png")
}
}
}
});
}
Edit 2
I changed how I set my user up
self.ref.child("users").child((user?.uid)!).setValue(["Email": email])
so now my database looks like this
users
*****uid*****
Email: "test#test.com
As I commented earlier: you'll need to check whether the query has any results by calling snapshot.hasChildren().
func checkIfEmailExists(textField: UITextField) {
let ref = FIRDatabase.database().reference()
let email = firstContainerTextField.text ?? ""
ref.child("userEmails").queryEqual(toValue: email)
.observe(.value, with: { snapshot in
if (!snapshot.hasChildren()) {
// User doesn't exist yet...
}
});
}
The following is the structure of the Firebase function you might be looking for (Swift 4):
Auth.auth().fetchProviders(forEmail: emailAddress, completion: {
(providers, error) in
if let error = error {
print(error.localizedDescription)
} else if let providers = providers {
print(providers)
}
})
If the email address is already registered to a user, you will get a list of the providers that the email address is used for. Otherwise, the list of providers will be empty, and thus the email address is not registered.