How to detect firebase user registration and signIn successfully in flutter - firebase

As per the documentation of FlutterFire, there are two different methods for signIn and signUp for email and password authentication.
For signUp
try {
UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: "barry.allen#example.com",
password: "SuperSecretPassword!"
);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
print('The account already exists for that email.');
}
} catch (e) {
print(e);
}
For signIn
try {
UserCredential userCredential = await FirebaseAuth.instance.signInWithEmailAndPassword(
email: "barry.allen#example.com",
password: "SuperSecretPassword!"
);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
}
By this code I can handle error by FirebaseAuthException and stop some loading progress. But how do I detect that user successfully register and do something as per success registration?

But how do I detect that user successfully register?
It is actually detailed in the documentation section that explains the code you mention in your question.
In particular, for registration the doc says:
The method is a two-step operation; it will first create the new
account (if it does not already exist and the password is valid) and
then automatically sign in the user in to that account. If you are listening to changes in authentication state, a new event will be
sent to your listeners.
To listen to changes in authentication state you can call the authStateChanges() method on your FirebaseAuth instance, as follows:
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
// !!!!! Here you know the user is signed-in !!!!!
print('User is signed in!');
}
});
For signIn, it is the same. The doc states that:
Once successful, if you are listening to changes in authentication
state, a new event will be sent to your listeners.
Refer to the registration case above.
So, in summary, on one hand you call the methods to either register (createUserWithEmailAndPassword()) or sign-in (signInWithEmailAndPassword()) and, on the other hand, you set a listener that listens to changes in authentication state. The listener alerts you when the call to one of the two methods is successful.
The on FirebaseAuthException catch (e) {...} blocks deals with the cases when the call to one of the two methods is not successful.
Finally, note the following article from the Firebase Developers Medium publication: it presents a slightly different approach, declaring async methods which check if the user object returned by the methods is null or not, and call setState() accordingly.
For example:
void _register() async {
final FirebaseUser user = (await
_auth.createUserWithEmailAndPassword(
email: _emailController.text,
password: _passwordController.text,
)
).user;
if (user != null) {
setState(() {
_success = true;
_userEmail = user.email;
});
} else {
setState(() {
_success = true;
});
}
}

Related

How to create a user in Firebase Auth with a custom UID? [duplicate]

This question already has answers here:
how to create FireBase new users with custom UID with flutter
(2 answers)
Closed 12 months ago.
How can I create a user in Firebase auth with a custom UID for me.
In my app, I am using Email and Password to create the users. So I also use the method:
try {
UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: "barry.allen#example.com",
password: "SuperSecretPassword!"
);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
print('The account already exists for that email.');
}
} catch (e) {
print(e);
}
Is there any way for me to provide the UID and keep better control of my users?
Something like (In the same method):
try {
UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: "barry.allen#example.com",
password: "SuperSecretPassword!"
//uid: "My own id"
);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
print('The account already exists for that email.');
}
} catch (e) {
print(e);
}
What can I do here?
I don't believe you can do it via the FlutterFire SDK, but through the Firebase Admin SDK (i.e. via a Cloud Function), and if you do, you have to use the generated token to authenticate.
https://firebase.google.com/docs/auth/admin/create-custom-tokens#create_custom_tokens_using_the_firebase_admin_sdk
Alternative: Since Email and Password Authentication already give you a unique UID, that UID is pretty strong for any user management, but in case you want to come up with your own, you could generate another one (still keeping the one from Firebase Email and Pwd Auth - just for reference purposes) and when adding your users to another collection, you could do like:
FirebaseFirestore.instance.collection('users').add({
'uid': userCredential.user.uid,
'sUid': 'YOUR_GENERATED_ONE',
'email': 'email',
// other user info
});
Something of that nature. My two cents.

Flutter FirebaseAuth: SignUp unable to handle error when the email address is used by another user

My code here, and the error pointed at -> await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: email.trim(), password: password.trim()); - in code below
Future SignUp() async {
try {
UserCredential result = await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: email.trim(), password: password.trim());
User user = result.user;
return user;
} on FirebaseAuthException catch (e) {
print(e);
if (e.code == 'email-already-in-use') {
setState(
() // setState rebuilds the entire build by modifying the code inside it
{
error = e.toString();
EmailExists =
true; //sets the emailExists variable to 'true' and rebuild
});
}
}
}
Error:
PlatformException (PlatformException(firebase_auth, com.google.firebase.auth.FirebaseAuthUserCollisionException: The email address is already in use by another account., {message: The email address is already in use by another account., additionalData: {}, code: email-already-in-use}, null))
By default Firebase Authentication only allows a single user to register with a specific email address in a project. When a second user tries to register with the same email address, you get the error that you have and you'll need to handle that scenario in your code. Typically you'll tell the user that the email address has already been registered and either ask them for their password, or tell them to use another email address.
If you want to allow multiple users to register with the same email address, you can enable that by changing the Multiple accounts per email address setting in the Sign-in methods pabel of the Firebase console.

Display error message outside build widget

I am using a Model class to authenticate user before registering or logging.the problem is that i don't know a way to print error message to the user in snackbar,because no widget is defined in this class.
How can i display error message to user from Model Class?
Model class:
class FireAuth {
static Future<User> registerUsingEmailPassword({
String name,
String email,
String password,
}) async {
FirebaseAuth auth = FirebaseAuth.instance;
User user;
try {
UserCredential userCredential = await auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
user = userCredential.user;
await user.updateDisplayName(name);
await user.reload();
user = auth.currentUser;
//check if email is registered before
//add user data to firestore
CollectionReference users = FirebaseFirestore.instance.collection('users');
users.doc(user.uid).set({
'uid':user.uid,
'img_url':'0',
'name': name,
'phone': '',
'email': email,
'job_title':'',
'university':'',
'procedures':'',
'expert_in':'',
})
.then((value) => print("User Added"))
.catchError(
(error) => print("Failed to add user: $error"));
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
print('The account already exists for that email.');
}
} catch (e) {
print(e);
}
return user;
}
}
I need 'The account already exists for that email.' error message to display to user,not only printing it in log.
Excellent question, and I'll try to answer in the general so as to benefit your overall pattern in handling this very important case.
Depending on BuildContext is a common inconvenience in flutter. And it often comes up, but for good reason. You can think of it like this: You need the context because you need to specify where in the tree that UI is going to show. Knowing how to handle these cases makes the difference between beginner and more advanced flutter developers.
So one way is to pass the BuildContext around, but I wouldn't recommended it.
Lets say I have a function foo that returns some Future Rather than change the signature of the function to accept context, you can simply await the function and use the context in the callback already in your UI. For example,
instead of
Future foo(BuildContext context) {
try {
// await some async process
// Use context to show success.
} catch (e) {
// Use context to show failure.
}
}
You can do this
GestureDetector(
onTap: () async {
try {
await foo();
// Use context to show success.
} catch (e) {
// Use context to show failure.
}
},
child: // some child
),
The point is in the second example the context is already there in the widget. The signuture of foo is simpler. It requires some restructuring. Here I'm assuming that the series of events is traced back to a GestureDetector but it could be anything else.

Future<UserData> returns a Future<UserData> instead of a UserData variable

I have a widget that uses a Future - async that returns a UserData variable, but for some reason, I'm getting a Future variable instead.
a line of code from a widget Build:
dynamic user = Auth.signIn(_email, _password);
Auth Class:
Future<UserData> signIn(String email, String password) async {
try{
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password
);
print('signed in!');
return _createUserFromFirebase(userCredential.user.uid, null);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
return null;
}
}
You could try something like
return Future.value(_createUserFromFirebase(userCredential.user.uid, null);
instead of
return _createUserFromFirebase(userCredential.user.uid, null);
Async cannot be used without it being in a function with await. You can't randomly retrieve an asynchronous variable randomly in the script without loading it asynchronously, because the future might not have the data ready to be used.
If you need the future variable's future value to be in a widget try using the FutureBuilder widget. This will make it so that the user data is loaded asynchronously and a place holder of your choice can be shown before it loads. This would allow you to use the UserData type in your widget.
Or you can get the user by having the button have an async variable that sets a variable in the widget from null to the UserData after signing in.
I've added a new future method inside the widget class that saves the user in a variable:
Future<void> getUserData(String email, String password) async{
_user = await _auth.signIn(email, password);
print('user: $_user');
}

Firebase + Flutter: can't lock access to unverified email accounts

I'd like to block out people who didn't verify their email so i figured out this code for sign up:
// sign up
Future signUp(String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} catch (e) {
print('An error has occured by creating a new user');
print(
e.toString(),
);
}
try {
final FirebaseUser _user = await _auth.currentUser();
await _user.sendEmailVerification();
} catch (error) {
print("An error occured while trying to send email verification");
print(error.toString());
}
try {
await _auth.signOut();
} catch (err) {
print(err);
}
}
and this for sign in:
//Sign In with Email and Pass
Future signInWithEmailAndPassword(String email, String password) async {
FirebaseUser _user = await FirebaseAuth.instance.currentUser();
if (_user != null && _user.isEmailVerified == true) {
try {
await _auth.signInWithEmailAndPassword(
email: email, password: password);
return _user;
} catch (e) {
return null;
}
} else {
return null;
}
}
_auth is just an instance of FirebaseAuth.
The problem is that i can login even if i didnt verify the email.
Firebase Auth doesn't stop accounts from signing in if the user hasn't verified their email address yet. You can check that property _user.isEmailVerified to find out the state of that validation after the user signs in, and you can determine from there what the user should see.
isEmailVerified can be a little bit of trouble to get working correctly.
Make sure you are calling
await FirebaseAuth.instance.currentUser()..reload();
before your are calling isEmailVerified also in my own experience and I don't know if this is just something I was doing wrong but this did not work from my Auth class this did not start working until I put the code directly in initState() of my widget that checks whether the user is verified. Like I said that part might have been something I did wrong. Like stated this will not listen for change you must check yourself either periodically or at a point that you know email is verified.
Future(() async {
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
await FirebaseAuth.instance.currentUser()
..reload();
var user = await FirebaseAuth.instance.currentUser();
if (user.isEmailVerified) {
timer.cancel();
Navigator.of(context).popAndPushNamed(HearingsScreen.routeName);
}
});
});
So it checks every 10 seconds to see if the user has verified their email not the most elegant solution. The page I have this on just displays a message 'Please verify your email' so its not like this is interrupting other code. If your app is performing other tasks this might not be an option for you. If you want to play around with isEmailVerified go ahead but i spent a week of headaches until i settled on this.

Resources