I am creating an auth flow for my mobile app. I received an error: "Cannot find user in scope" on "self.userSession = user" & ".document(.user.uid)" I'm not sure how to resolve this error. Any help would be appreciated!
`
import SwiftUI
import Firebase
import FirebaseCore
import FirebaseAuth
import FirebaseStorage
import FirebaseFirestore
import FirebaseFirestoreSwift
class AuthViewModel: ObservableObject {
#Published var userSession: FirebaseAuth.User?
init() {
self.userSession = Auth.auth().currentUser
print("DEBUG: User session is \(self.userSession?.uid)")
}
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
}
guard let user = result?.user else { return }
self.userSession = user
print("DEBUG: Did log user in..")
}
}
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
}
print("DEBUG: Registered user successfully.")
print("DEBUG: User is \(self.userSession)")
let data = ["email": email,
"fullname": fullname,
"uid": user.uid]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { _ in
print("DEBUG: Did upload user data..")
}
}
}
func signOut() {
// sets user session to nil so we show login view
userSession = nil
// signs user out on server
try? Auth.auth().signOut()
}
}
`
I have tried moving the declaration up on to the viewmodel but that caused another error: "cannot find result in scope".
You should use an auth state change listener to save user session as mentioned in the Firebase documentation. You should also not initialize the auth object every time in every function.
import FirebaseAuth
class AuthViewModel: Observable Object {
private let auth: Auth = Auth.auth() // You can also use dependency injection
#Published var userSession: User?
private var authStatusHandler: AuthStateDidChangeListenerHandle?
func addAuthStatusListener() {
if let handle = authStatusHandler {
auth.removeStateDidChangeListener(handle)
}
authStatusHandler = auth.addStateDidChangeListener { _, user in
self.userSession = user
}
}
...
}
In the register function, you try to access the "user" variable which is defined in the login function. This variable is out of the scope of the register function. You should use the userSession in the class for the register function.
func register(withEmail email: String, password: String, fullname: String) {
...
let data = ["email": email,
"fullname": fullname,
"uid": userSession.uid] // <- Here
Firestore.firestore().collection("users")
.document(userSession.uid) // <- Here
.setData(data) { _ in
print("DEBUG: Did upload user data..")
}
...
}
Related
In one view, I have this func to signUp and get userName
func signUp() {
if self.email != "" {
if self.password == self.repass {
session.signUp(email: email, password: password) { (result, error) in
if error != nil {
self.error = error!.localizedDescription
//self.error = "A senha deve ter 6 caracteres ou mais"
}
else{
self.email = ""
self.password = ""
let db = Firestore.firestore()
db.collection("Users").document(result!.user.uid).setData(["userName": userName]) { (error) in
if error != nil{
}
}
}
}
}
else{
self.error = "Senhas não coincidem"
}
}
else{
self.error = "Complete todos espaços"
}
}
In other view, I want to get the userName the user typed ad display it, but for this I need to link both views to get access to func SignUp. How to do it?
EDIT:
Based on the answer I was given, I did the corresponding changes:
SessionStore:
#Published var session: User? = nil
var handle: AuthStateDidChangeListenerHandle?
#Published var userName: String = ""
func listen() {
handle = Auth.auth().addStateDidChangeListener({ [self] (auth, user) in
if let user = user {
self.session = User(uid: user.uid, email: user.email!, userName: userName)
}
else {
self.session = nil
}
})
}
struct User {
var uid:String
var email:String
var userName: String
}
HomeView ----> var userName: User
Text(userName.userName)
explaining how my code works: the #main view is MedAppApp: App. From there, the code goes to ContentView. if session.session != nil, TransitionView, else, SignView. Continuing from SignView, there I have both SignInView and SignUpView views, which they have Button(action: signIn) and Button(action: Signup), respectively. Continuing from TransitionView, there is where I have:
struct TransitionView: View {
#StateObject var LocationModel = LocationViewModel()
#State var userName = User(uid: "", email: "", userName: "")
var body: some View {
HomeView(LocationModel: LocationModel, userName: userName)
}
}
#State var userName =... and userName: userName needed to be added
The app launches, I can signUp with username (and its stored in Firestore with uid as document), but the userName isn't displayed. I think the problem must be in User(uid: "", email: "", password: ""). I didn't know what to add there.
By the way, Im thinking about maintaining the code and then get the userName by using the document id (that already is the user uid) so I can access it. Don't have a final answer yet, but Im working on it.
EDIT2:
I added #StateObject var session = SessionStore()
Tried to use:
#State var userName = User(uid: SessionStore().session?.uid ?? "", email: SessionStore().session?.email ?? "", userName: SessionStore().session?.userName ?? "")
Still doesn't work
Your question is too open-ended, but I'll provide an answer that hopefully will set you up on the right direction.
First, in SwiftUI, separate the concept of a view (i.e. how things are arranged on the screen) from the concept of data that drives the views. What I mean by that is, if you need some data, like userName, the code that obtains it shouldn't know or care about which view will make use of it. And Views, shouldn't care how the data is obtained.
Second, in SwiftUI, views should not be thought of as "entities" (like in UIKit) with their own life cycles. They are just declarative statements that define how the UI is arranged.
For example, suppose you have this (fake) class that signs the user in, and when signed-in, populates the userName property. The instance of this class is a view model.
struct User {
let userName: String
}
class Session: ObservableObject {
#Published var user: User? = nil
func signIn(_ completion: (User) -> ()) {
DispatchQueue.main.asyncAfter(.now() + 0.5) {
self.user = User(userName: "Mateus") // fake sign-in
completion(self.user) // not really used in this example
}
}
}
#Published and ObservableObject in combination are how SwiftUI views know to react to changes to this object. Read more about them, if you don't know what they do.
Next, design a root view that decides what inner view to display depending on whether the user is signed in or not:
struct RootView: View {
#StateObject var session = Session()
var body: some View {
VStack() {
if let user = session.user {
ContentView(user: user) // view is conditionally rendered
} else {
Button("sign in") { self.signIn() }
}
}
}
func signIn() {
self.session.signIn() {
print("signed in...!")
}
}
}
#StateObject manages the life cycle of the view model for the view.
As you see above, ContentView will only be rendered when session.user is not nil (i.e. when user has signed in), so the view is driven by changes in data. I did not need to create a ContentView instance anywhere ahead of time.
Then ContentView is straightforward:
struct ContentView: View {
var user: User
var body: some View {
Text("signed-in username: \(user.userName)")
}
}
I am creating a project whereby a user database is automatically created on signup for the first time. It functions well, however, i want the information on the database to display the User's Email or Name and not uid as shown below.
the only reason i am using uid is because i have failed to successfully manage to call a function that enables me to display it.
here is my code:
they belong in an Object activity so the first 2 code examples are object codes
initiate the firebase setup
object FirestoreUtil {
private val FirestoreInstance: FirebaseFirestore by lazy { FirebaseFirestore.getInstance() }
private val currentUserDocRef: DocumentReference
get() = FirestoreInstance.document(
"users/${FirebaseAuth.getInstance().currentUser?.uid
?: throw NullPointerException("UID is null.")}"
)
2.THIS is used on the signup button to create a new users database on launching;
fun InitCurrentUserIfFirsttime(onComplete: () -> Unit) {
currentUserDocRef.get().addOnSuccessListener { documentSnapshot ->
if (!documentSnapshot.exists()) {
val newUser = User(
FirebaseAuth.getInstance().currentUser?.displayName ?: "",
"", "",null, mutableListOf()
)
currentUserDocRef.set(newUser).addOnSuccessListener {
onComplete()
}
}else{
onComplete()
}
}
}
3.My data class;
data class User(val name: String,
val bio: String,
val age: String,
val profilePicturePath: String?,
val registrationTokens: MutableList<String>) {
constructor(): this("", "", "",null, mutableListOf())
}
4.from my signup button;
auth.createUserWithEmailAndPassword(signuptv.text.toString(), signupPassword.text.toString())
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
FirestoreUtil.InitCurrentUserIfFirsttime {
startActivity(Intent(this, LogInActivity::class.java))
finish()
}
} else {
Toast.makeText(baseContext, "Authentication failed.",
Toast.LENGTH_SHORT).show()
}
}
How can I pass data to a view and use it directly in the "header"? All tutorials I made are accessing the data in the view body - which works fine - but I want to call a graphlql method from the UpdateAccountView and than render a view based on the result.
My class for passing data:
class Account {
var tel: Int
init(tel: Int) {
self.tel = tel
}
}
My main view where the class is initialised (simplified - normally the "tel" will come from an input)
struct ContentView: View {
var account: Account = Account(tel: 123)
var body: some View {
NavigationView {
NavigationLink(
destination: UpdateAccountView(account: account),
label: {
Text("Navigate")
})
}
}
}
The view I call to do the request and call the next view based on the result
UpdateAccount is taking tel:Int as a parameter.
And here is the problem. I cannot access account.tel from the passed data.
struct UpdateAccountView: View {
var account: Account
#ObservedObject private var updateAccount: UpdateAccount = UpdateAccount(tel: account.tel)
#ViewBuilder
var body: some View {
if updateAccount.success {
AccountVerifyView()
} else {
ContentView()
}
}
}
The error:
Cannot use instance member 'account' within property initializer; property initializers run before 'self' is available
Update method (GraphQL):
class UpdateAccount: ObservableObject {
#Published var success: Bool
init(tel: Int){
self.success = false
update(tel: tel)
}
func update(tel: Int){
Network.shared.apollo.perform(mutation: UpdateAccountMutation(tel: tel)) { result in
switch result {
case .success(let graphQLResult):
self.success = graphQLResult.data!.updateAccount.success
case .failure(let error):
print("Failure! Error: \(error)")
self.success = false
}
}
}
I saw that there is an EnvironmentObject but than the variable become available globally as far as I understood, which is not necessary here.
Thank you for your help.
You can make it in explicit init, like
struct UpdateAccountView: View {
var account: Account
#ObservedObject private var updateAccount: UpdateAccount // << declare
init(account: Account) {
self.account = account
self.updateAccount = UpdateAccount(tel: account.tel) // << here !!
}
// ... other code
}
I just have basic code for testing purposes.
let user = PFObject(className: "User")
user["email"] = userEmail
user.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
....
}
But I always get this result.
Is the main User class named differently?
You should be using PFUser rather than creating a PFObject with the classname of "User"
let user = PFUser()
user["email"] = userEmail
user.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
...
}
Is there a good way to embed initialized struct variables in another struct?
Consider the following situation:
type Account struct {
AdminUser, AdminPass string
}
const (
acc1: "account_user",
pass: "111222"
)
var AccountDef = Account {
AdminUser: "acc1",
AdminPass: "pass1"
}
type Login struct {
Acc *AccountDef
Username, Password, Token string
}
var LoginDef = Login {
Token: "adaasdasddas1123"
}
I want to reuse AccountDef in Login, then I want to instantiate LoginDef in another function then use it for rendering templates like LoginDef.Acc.AdminUser
Is this possible to do?
If you want a Login to contain the fields from an Account, you can embed them like so:
http://play.golang.org/p/4DXnIsILd6
type Account struct {
AdminUser string
AdminPass string
}
type Login struct {
*Account
Username, Password, Token string
}
func main() {
acct := &Account{
AdminUser: "username",
AdminPass: "pass",
}
login := Login{Account: acct}
fmt.Println("login.AdminUser:", login.AdminUser)
fmt.Println("login.AdminPass:", login.AdminPass)
}