Add user using Authentication + Create User - swift

Currently, I have successfully implemented allowing a user to create an account using an email address and password and inserting that info into Firebase Authentication. What I'm trying to figure out now is how to simultaneously take the information entered in the sign up form and insert a new record into FireStore. I'd like to insert the user's email address, first and last name as a record.
I've tried adding the code to insert the record inside of my "SignUp" function, but that seems to break the function. Below is the working code followed by the code I tried that didn't work.
//Working Code
struct SignUpView: View {
#State var email: String = ""
#State var password: String = ""
#State var error: String = ""
#EnvironmentObject var session: SessionStore
let db = Firestore.firestore()
func signUp() {
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
} else {
self.email = ""
self.password = ""
}
}
}
//Does Not Work (Error: Type 'SignUpView' does not conform to protocol 'View')
struct SignUpView: View {
#State var email: String = ""
#State var password: String = ""
#State var error: String = ""
#State var fname: String = ""
#State var lname: String = ""
#EnvironmentObject var session: SessionStore
let db = Firestore.firestore()
func signUp() {
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
} else {
self.email = ""
self.password = ""
}
}
// Insert record into Users collection
db.collection("users").addDocument(data: [
"fname": fname,
"lname":lname,
"email": email ])
}
// End Insert
}

Session.SignUp is asynchronous.
struct SignUpView: View {
#State var email: String = ""
#State var password: String = ""
#State var error: String = ""
#State var fname: String = ""
#State var lname: String = ""
#EnvironmentObject var session: SessionStore
let db = Firestore.firestore()
func signUp() {
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
} else {
self.email = ""
self.password = ""
// Insert record into Users collection
db.collection("users").addDocument(data: [
"fname": self.fname,
"lname":self.lname,
"email": self.email ])
}
// End Insert
}
}
}
But the code you posted can not be the full code for your SignUpView because it needs to contain
var body: some View {}

Adding a user model to Firestore in SwiftUI is very simple. I used a completionBlock to handle completion, you can show a loading indicator till the function returned a value. This may be useful for all queries.
The following code will add a user to Firestore:
func addUserToDatabase(name: String, email: String, phone: String, completionBlock: #escaping (_ success: Bool) -> Void) {
let accountData = [
"name" : name,
"email" : email,
"phone" : phone
]
db.collection("users").document(self.user.id).setData(accountData) { err in
if err != nil {
completionBlock(false)
} else {
completionBlock(true)
}
}
}
Note that: 'self.user.id' is the user ID from the authentication. So when you create a user in the firebaseAuth, a unique ID will be created, you can store this ID in your app so you can create a reference in your database. Useful for easily removing or changing user's data.
Example of using this method in your app
self.addUserToDatabase(name: name, email: email, phone: phone) { (succes) in
if (succes) {
print("User added to the database")
UserDefaults.standard.set(self.user.id, forKey: "uid_current_user")
completionBlock(true)
} else {
print("Something went wrong when trying to add user to the database")
completionBlock(false)
}
}

Related

Need acces from document to collection Firestore

I'm trying to do an iOS app and i've binded it with firebase, so I'm trying to get some posts ad fetch them, and this works fine, however this posts got 2 collections (likes and replies) and i'm trying to fetch likes, the thing is that I can't get the likes because for some reasons I can't a class for document forEach neither I can't access it, someone got any idea?
Code:
import Foundation
import Firebase
struct Post : Hashable {
var id : String
var dateAdded : String
var posterEmail : String
var posterUsername : String
var posterIcon : String
var postTitle : String
var postBody : String
var likes : [String]
var userLikedPost : Bool
}
struct Like {
var likeId : String
var likerEmail : String
}
class Likes {
var likes : [Like] = []
func fetchLikes() {
//Firestore.firestore()
}
}
class Posts : ObservableObject {
#Published var posts : [Post] = []
func fetchPosts() {
Firestore.firestore().collection("posts").getDocuments(completion: { (docPosts, error) in
if (error != nil) {
print("error fetching posts")
} else {
docPosts?.documents.forEach { (post) in
let id = post.documentID
let email = post.get("posterEmail") as! String
let username = post.get("posterUsername") as! String
let icon = post.get("posterIcon") as! String
let title = post.get("title") as! String
let body = post.get("body") as! String
// Here i want to insert the code that gets the likes class and access the likes variable
self.posts.append(Post(id: id, dateAdded:String(id.split(separator: "_").joined(separator: "/").prefix(10)) ,posterEmail: email, posterUsername: username, posterIcon: icon, postTitle: title, postBody: body,
likes: [],userLikedPost: false))
}
}
})
}
}
The Firestore structure was not included in the question so I will present one for use
user_wines
uid_0
name: "Jay"
favorite_wines:
0: "Insignia"
1: "Scavino Bricco Barolo"
2: "Lynch Bages"
uid_1
name: "Cindy"
favorite_wines
0: "Palermo"
1: "Mercury Head"
2: "Scarecrow"
And then the code to read all of the user documents, get the name, the wine list (as an array as Strings) and output it to console
func readArrayOfStrings() {
let usersCollection = self.db.collection("user_wines")
usersCollection.getDocuments(completion: { snapshot, error in
guard let allDocs = snapshot?.documents else { return }
for doc in allDocs {
let name = doc.get("name") as? String ?? "No Name"
let wines = doc.get("favorite_wines") as? [String] ?? []
wines.forEach { print(" ", $0) }
}
})
}
and the output
Jay
Insignia
Scavino Bricco Barolo
Lynch Bages
Cindy
Palermo
Mercury Head
Scarecrow
EDIT
Here's the same code using Codable
class UserWineClass: Codable {
#DocumentID var id: String?
var name: String
var favorite_wines: [String]
}
and the code to read data into the class
for doc in allDocs {
do {
let userWine = try doc.data(as: UserWineClass.self)
print(userWine.name)
userWine.favorite_wines.forEach { print(" ", $0) }
} catch {
print(error)
}
}

Function being called twice due to handlers, how to fix it so it's only called once?

I am new to Swift programming and am trying to understand the concept of handlers. My saveDataToFirestore function is being called twice - I'm pretty it's due to the completion handlers, but I can't figure it out
I have a button in the SignUpView that the user presses once they've inputted their information
struct SignUpView: View {
#ObservedObject var user = UserViewModelTEMP()
...
user.signUp(firstName: firstName, lastName: lastName, email: email, password: password, reenterPassword: passwordReEnter) { (result, error) in
if error != nil {
self.error = true
} else {
self.isSignedUp = true
self.password = ""
}
}
...
This is the ViewModel where first the signUp function is called, then the user's name and email is passed to another function so that their info can be save to the database
class UserViewModelTEMP : ObservableObject {
#Published var user = [UserTEMP]()
var handle: AuthStateDidChangeListenerHandle?
private let db = Firestore.firestore()
func signUp (firstName: String, lastName: String, email: String, password: String, reenterPassword: String, handler: #escaping AuthDataResultCallback) {
//Ensure that passwords match
if reenterPassword != password {
print("Passwords do not match") // make this into a popup
return
}
//Authenticate with Firebase
Auth.auth().createUser(withEmail: email, password: password, completion: handler)
// Save user data to Firestore
saveDataToFirestore(firstName: firstName, lastName: lastName, email: email)
}
func saveDataToFirestore(firstName: String, lastName: String, email: String) {
print("entry one")
// ensure user is signed in
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
print("entry 2")
if let user = user {
//if we have a user, create a new user model and save to Firebase
print("Got user: \(user.uid)")
let ref = self.db.collection("users").document(user.uid)
ref.setData([
"first_name" : firstName,
"last_name" : lastName,
"email" : email
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added: \(ref.documentID)")
}
}
} else {
// when we don't have a user, set the session to nil
//self.user = []
}
}
}
Output looks like this
entry one
entry 2
Got user: <user_id_1>
Document added: <document>
entry 2
Got user: <user_id_2>
Document added: <document>
Side note, the user_ids in the output don't match, when they should (even if being called twice).
I am unsure how to fix this problem

Vapor 4: Children relation not eager loaded, use $ prefix to acces

Currently I am working on a school assignment where we have to build an API using Vapor. I have a few basic API calls working and I am trying a bit more advanced API calls now but I can't get this to work.
I have this function addToParty that is being called when the URL /party/join/:partyID is called with a body
{
"id": "CC1FAC6B-A2B3-471C-A488-147300196981",
"username": "string",
"is_ready": true
}
I am trying to find a party by the partyId and add the user to the list of users of the party.
func addToParty (req: Request) throws -> EventLoopFuture<Party.Output> {
guard let id = req.parameters.get("partyID", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(Party.JoinParty.self)
return Party.find(id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { element in
element.users.append(User(id: UUID(input.id), username: input.username, is_ready: input.is_ready))
return element.save(on: req.db)
.map{ Party.Output(code: "200") }
}
}
When I try the code above I get the error Fatal error: Children relation not eager loaded, use $ prefix to access: Children<Party, User>(for: [party_id]): file FluentKit/Children.swift, line 33 from the line
element.users.append(User(id: UUID(input.id), username: input.username, is_ready: input.is_ready))
When I comment this line the code runs and I get a return code.
I tried adding the prefix to element.$users and $User but then it complains about not being able to find element.$users and $User in scope.
Party model
import Fluent
import Vapor
final class Party: Model, Content {
static let schema = "parties"
struct JoinParty: Content {
let id: String
let username: String
let is_ready: Bool
}
struct Output: Content {
let code: String
}
#ID(key: .id)
var id: UUID?
#Field(key: "party_code")
var party_code: String
#Field(key: "host_id")
var host_id: UUID
#Field(key: "is_active")
var is_active: Bool
// change to Game when model is made
#Field(key: "selected_games")
var selected_games: [String]?
// change to Setting when model is made
#Field(key: "settings")
var settings: String
#Field(key: "results")
var results: Array<GameResult>?
#Children(for: \.$party)
var users: [User]
init() { }
init(id: UUID? = nil,
party_code: String,
host_id: UUID,
is_active: Bool,
selected_games: [String]? = nil,
settings: String,
results: Array<GameResult>? = nil) {
self.id = id
self.party_code = party_code
self.host_id = host_id
self.is_active = is_active
self.selected_games = selected_games
self.settings = settings
self.results = results
}
}
User model
import Fluent
import Vapor
final class User: Model, Content {
static let schema = "users"
struct Input: Content {
let id: UUID
let username: String
}
struct Output: Content {
let id: String
let username: String
}
#ID(key: .id)
var id: UUID?
#Field(key: "username")
var username: String
#Field(key: "is_ready")
var is_ready: Bool
#OptionalParent(key: "party_id")
var party: Party?
#Children(for: \.$user)
var gameResults: [GameResult]
init() {}
init(id: UUID? = nil, username: String, is_ready: Bool, partyID: UUID? = nil) {
self.id = id
self.username = username
self.is_ready = is_ready
self.$party.id = partyID
}
}
I have a similar function to update a username from a user already working which is almost the same thing.
func update(req: Request) throws -> EventLoopFuture<User.Output> {
let input = try req.content.decode(User.Input.self)
return User.find(input.id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
user.username = input.username
return user.save(on: req.db)
.map { User.Output(id: user.id!.uuidString, username: user.username) }
}
}
Any help would be really appreciated.
Thank you in advance.
Unfortunately, adding children to a parent model is not that intuitive yet. I hope that someday we can get that added, but it's not there yet. Fluent 5 maybe?
Anyway, what you'll need to do instead is create your new User model, passing in the party's ID value to the partyID initializer parameter, and then save the User model.
let user = User(id: UUID(input.id), username: input.username, is_ready: input.is_ready, partyID: element.id)
return user.save(on: request.db)
So your method should end up looking like this:
func addToParty(req: Request) throws -> EventLoopFuture<Party.Output> {
guard let id = req.parameters.get("partyID", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(Party.JoinParty.self)
return Party.find(id, on: req.db).unwrap(or: Abort(.notFound)).flatMap { element in
return User(
id: UUID(input.id),
username: input.username,
is_ready: input.is_ready,
partyID: element.id
).save(on: req.db)
}.transform(to: Party.Output(code: "200"))
}

Cannot convert return expression of type 'User' to return type 'User?' for getting currentUser from Firebase

I'm new to Swift and I'm having trouble returning a User? value from currentUser in Firebase for CURRENT_USER. I have my User class declaration below and it's located in a separate UserApi file. All the help is greatly appreciated!
var CURRENT_USER: User? {
if let currentUser = Auth.auth().currentUser {
return currentUser
}
return nil
}
User declaration:
class User {
var email: String?
var profileImageUrl: String?
var username: String?
var id: String?
var isFollowing: Bool?
}
extension User {
static func transformUser(dict: [String: Any], key: String) -> User {
let user = User()
user.email = dict["email"] as? String
user.profileImageUrl = dict["profileImageUrl"] as? String
user.username = dict["username"] as? String
user.id = key
return user
}
}
Since you have declared a User type in your project, the word User in var CURRENT_USER: User? { gets resolves to your own User type, rather than FirebaseAuth.User, which is in another module.
To fix this, either:
rename your own User to something such as UserInfo, or;
Add the prefix FirebaseAuth. to User to differentiate:
var currentUser: FirebaseAuth.User? { Auth.auth().currentUser }
You can also add a typealias to give the Firebase User a different name:
typealias FirebaseUser = FirebaseAuth.User

How should I retrieve data from firebase and put it into a dictionary?

I have an organization document with a Members collection inside of it and then a members document inside of that. Inside the members document includes a Map of a user which is a member. The Key is the UserID and 3 values (firstName, lastName, username). I am trying to load in this data into my "Event" class that holds a membersInvited Property that is a dictionary. Inside the Event class is a method to get this data called getOrgMembers(). Even though I have that data in firebase I am getting a nil value for my dictionary. I also am using Dispatch but kind of new to it.
Below is code in the Event Class:
var membersInvited: [Member: Bool]?
func getOrgMembers(dispatch: DispatchGroup?) {
let membRef = BandzDatabase.collection("Organizations").document(currentUser.currentOrgID!).collection("Members").document("members")
membRef.getDocument { (snapshot, error) in
if let error = error {
print (error.localizedDescription)
} else {
if let data = snapshot?.data() {
for (key,value) in data {
if let membArray = value as? [String: Any] {
let username = membArray["username"] as? String
let firstName = membArray["firstName"] as? String
let lastName = membArray["lastName"] as? String
let userID = key
let member = Member(username: username ?? "", firstName: firstName ?? "", lastName: lastName ?? "", userID: userID)
self.membersInvited?.updateValue(true, forKey: member)
}
}
}
}
dispatch?.leave()
}
}
struct Member: Hashable {
var username: String
var firstName: String
var lastName: String
var userID: String
init (username: String, firstName: String, lastName: String, userID: String) {
self.username = username
self.firstName = firstName
self.lastName = lastName
self.userID = userID
}
}
Below is were I call this method from another class:
func getMembers() {
showActivityIndicatory(uiView: self.view)
self.dispatchGroup.enter()
eventMade?.getOrgMembers(dispatch: self.dispatchGroup)
self.dispatchGroup.notify(queue: .main) {
//self.tableView.reloadData()
stopActivityIndicator()
print("happens")
print(self.eventMade?.membersInvited)
}
}
After some research, I discovered that since I never initilized the dictionary, whenever I was calling to append key-value paires it would not even run since it was an optional. So I changed the decleration to this:
var membersInvited: [Member: Bool] = [:]