I'm trying to use SwiftUI + Firebase Authentication via the email/password login. My question is, is there any way to append profile information to the user's authentication information when they create an account, or would I have to use Firebase Auth in tandem with Firestore or Firebase Database? I'm just trying to collect the user's first and last name and possibly city/state/country.
import SwiftUI
import Firebase
import Combine
class SessionStore: ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
@Published var session: User? {didSet {self.didChange.send(self) }}
var handle: AuthStateDidChangeListenerHandle?
func listen() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
self.session = User(uid: user.uid, email: user.email, displayName: user.displayName)
} else {
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 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?
var displayName: String?
init(uid: String, email: String?, displayName: String?) {
self.uid = uid
self.email = email
self.displayName = displayName
}
}
You can use Firebase Authentication to allow users to sign in to your app using one or more sign-in methods, including email address and password sign-in, and federated identity providers such as Google Sign-in and Facebook Login.
Create a Sign-in Flow To begin authentication, a simple Sign-In button is used. In this stage, you'll implement the logic for signing in with Google and then authenticating with Firebase using that Google account. mFirebaseAuth = FirebaseAuth. getInstance();
How does it work? To sign a user into your app, you first get authentication credentials from the user. These credentials can be the user's email address and password, or an OAuth token from a federated identity provider. Then, you pass these credentials to the Firebase Authentication SDK.
Firebase Authentication manages user authentication - while it does store additional information provided by some of the federated identity providers (such as profile picture URL), it isn't intended to be a profile management solution. If you'd like to store additional information about your users, you can implement this using Cloud Firestore or Firebase Realtime Database.
Please note that the Custom Claims feature is intended for managing advanced role management features - the documentation actually discourages developers from using this feature for storing additional user information, as custom claims are in fact stored in the ID token.
As @krjw correctly mentions, you don't need to use PassthroughObject
when using @Published
.
Here is a sample implementation:
// File: UserProfileRepository.swift
import Foundation
import Firebase
import FirebaseFirestoreSwift
struct UserProfile: Codable {
var uid: String
var firstName: String
var lastName: String
var city: String
}
class UserProfileRepository: ObservableObject {
private var db = Firestore.firestore()
func createProfile(profile: UserProfile, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
do {
let _ = try db.collection("profiles").document(profile.uid).setData(from: profile)
completion(profile, nil)
}
catch let error {
print("Error writing city to Firestore: \(error)")
completion(nil, error)
}
}
func fetchProfile(userId: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
db.collection("profiles").document(userId).getDocument { (snapshot, error) in
let profile = try? snapshot?.data(as: UserProfile.self)
completion(profile, error)
}
}
}
// File: SessionStore.swift
import Foundation
import Combine
import Firebase
class SessionStore: ObservableObject {
@Published var session: User?
@Published var profile: UserProfile?
private var profileRepository = UserProfileRepository()
func signUp(email: String, password: String, firstName: String, lastName: String, city: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
Auth.auth().createUser(withEmail: email, password: password) { (result, error) in
if let error = error {
print("Error signing up: \(error)")
completion(nil, error)
return
}
guard let user = result?.user else { return }
print("User \(user.uid) signed up.")
let userProfile = UserProfile(uid: user.uid, firstName: firstName, lastName: lastName, city: city)
self.profileRepository.createProfile(profile: userProfile) { (profile, error) in
if let error = error {
print("Error while fetching the user profile: \(error)")
completion(nil, error)
return
}
self.profile = profile
completion(profile, nil)
}
}
}
func signIn(email: String, password: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if let error = error {
print("Error signing in: \(error)")
completion(nil, error)
return
}
guard let user = result?.user else { return }
print("User \(user.uid) signed in.")
self.profileRepository.fetchProfile(userId: user.uid) { (profile, error) in
if let error = error {
print("Error while fetching the user profile: \(error)")
completion(nil, error)
return
}
self.profile = profile
completion(profile, nil)
}
}
}
func signOut() {
do {
try Auth.auth().signOut()
self.session = nil
self.profile = nil
}
catch let signOutError as NSError {
print("Error signing out: \(signOutError)")
}
}
}
For the UI:
// File: ContentView.swift
import SwiftUI
struct ContentView: View {
@State var firstName: String = ""
@State var lastName: String = ""
@State var city: String = ""
@State var email: String = ""
@State var password: String = ""
@State var confirmPassword: String = ""
@State var showSignUpForm = true
@State var showDetails = false
@ObservedObject var sessionStore = SessionStore()
@State var profile: UserProfile?
var body: some View {
NavigationView {
VStack {
if self.showSignUpForm {
Form {
Section {
TextField("First name", text: $firstName)
.textContentType(.givenName)
TextField("Last name", text: $lastName)
.textContentType(.familyName)
TextField("City", text: $city)
.textContentType(.addressCity)
}
Section {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocapitalization(.none)
SecureField("Password", text: $password)
SecureField("Confirm password", text: $confirmPassword)
}
Button(action: { self.signUp() }) {
Text("Sign up")
}
}
.navigationBarTitle("Sign up")
}
else {
Form {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocapitalization(.none)
SecureField("Password", text: $password)
Button(action: { self.signIn() }) {
Text("Sign in")
}
}
.navigationBarTitle("Sign in")
}
Button(action: { self.showSignUpForm.toggle() }) {
Text(self.showSignUpForm ? "Have an account? Sign in instead." : "No account yet? Click here to sign up instead.")
}
}
.sheet(isPresented: $showDetails) {
UserProfileView(userProfile: self.profile ?? UserProfile(uid: "", firstName: "", lastName: "", city: ""))
}
}
}
func signUp() {
sessionStore.signUp(email: self.email, password: self.password, firstName: self.firstName, lastName: self.lastName, city: self.city) { (profile, error) in
if let error = error {
print("Error when signing up: \(error)")
return
}
self.profile = profile
self.showDetails.toggle()
}
}
func signIn() {
sessionStore.signIn(email: self.email, password: self.password) { (profile, error) in
if let error = error {
print("Error when signing up: \(error)")
return
}
self.profile = profile
self.showDetails.toggle()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// File: UserProfileView.swift
import SwiftUI
struct UserProfileView: View {
var userProfile: UserProfile
var body: some View {
NavigationView {
Form {
Text(userProfile.uid)
Text(userProfile.firstName)
Text(userProfile.lastName)
Text(userProfile.city)
}
.navigationBarTitle("User \(userProfile.uid)")
}
}
}
struct UserProfileView_Previews: PreviewProvider {
static var previews: some View {
let userProfile = UserProfile(uid: "TEST1234", firstName: "Peter", lastName: "Friese", city: "Hamburg")
return UserProfileView(userProfile: userProfile)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With