Firebase Authentication with Flutter not working - firebase

I am trying to create a signup page which should give an error message if user with particular email id already exist. But it's not working.
signUp() {
if (formkey.currentState!.validate()) {
Map<String, String> userDataMap = {
"name": usernameC.text,
"email": emailC.text
};
setState(() {
isLoading = true;
});
authMethods.signUp(emailC.text, passwordC.text).then((value) {
databaseMethods.uploadUserData(userDataMap);
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => ChatRoom()));
});
}
}
It calls the signUp() function from auth.dart given below
UserData? _userFromFirebase(User? user) {
return user != null ? UserData(userid: user.uid) : null;
}
Future signUp(String email, String pass) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: pass);
User? user = result.user;
return _userFromFirebase(user);
} catch (e) {
print(e}
}
Every time I signup with same email it doesn't give any error.

If you sign up with the same email you should get this message:
[firebase_auth/email-already-in-use] The email address is already in use by another account.
I use print(e.hashCode) and then use this hash code to show an error message.

Ok I tried this method and it worked out. Just added null check for the "value" attribute in.
authMethods.signUp(emailC.text, passwordC.text).then((value)
It was returning null without any other message. That's why I was unable to see the error.

Related

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.

Flutter Firebase login error but still being logged in?

EDIT: The login functions is somehow called twice, once with the correct credentials and the other time the email and password String are empty.
This weird hack seems to fix it, but I cannot see why the login function is called twice:
if (email.isEmpty && password.isEmpty) {
return;
}
I have a weird problem that is caused by Firebase-Auth, I believe. Quick summary of the process:
User logs in normally, then authenticates with the local_auth package using biometrics
If that is successful, the login credentials (email, password) are stored on the device using FlutterSecureStorage
Then, on every new app startup, the user will be prompted with the local_auth and if that is successful, I call the login method with the credentials read from the device.
Here comes the error: I get a FirebaseAutException with the error message: given String is empty or null, but then I am being logged in, even though there was an error.
This is the login code:
void login({required String email, required String password}) async {
try {
final _result = await _auth.signInWithEmailAndPassword(
email: email.trim(),
password: password.trim(),
);
if (_result.user != null) {
print("HERE1");
if (!_result.user!.emailVerified) {
Get.to(() => EmailVerificationScreen());
}
if (!await credentialsSaved) {
Get.to(
() => BiometricsPage(
email: email.trim(),
password: password.trim(),
),
);
}
Get.find<UserController>().setUser = await Database().getUser(
_result.user!.uid,
);
loggedIn.value = true;
}
} on FirebaseAuthException catch (e) {
print("HERE2");
Get.snackbar(
"Error logging in",
e.message!,
snackPosition: SnackPosition.BOTTOM,
snackStyle: SnackStyle.FLOATING,
margin: EdgeInsets.all(10),
);
}
}
The print statements occur in the following order:
HERE1
HERE2
EDIT: After a few tries, I saw that sometimes the print-order was exactly the other way around :/
This is the code I use for retrieving the credentials from the device:
void checkLocalBiometrics() async {
if (await credentialsSaved) {
var localAuth = LocalAuthentication();
bool canCheckBiometrics = await localAuth.canCheckBiometrics;
if (canCheckBiometrics) {
var didAuthenticate = await localAuth.authenticate(
localizedReason: "-----------------",
biometricOnly: true,
stickyAuth: true,
);
if (didAuthenticate) {
var secureStorage = FlutterSecureStorage();
var storedEmail = await secureStorage.read(key: "email");
var storedPassword = await secureStorage.read(key: "password");
login(email: storedEmail!, password: storedPassword!);
}
}
}
}
This is the credentialsSaved method:
Future<bool> get credentialsSaved async {
var secureStorage = FlutterSecureStorage();
var storedEmail = await secureStorage.read(key: "email");
return storedEmail != null;
}
I can guarantee that the result of the secureStorage.read(...) is not null since I check that in the credentialsSaved method. What am I missing here?
I finally found the solution. Turns out that the LoginButton I was using in my LoginPage has an open issue about functions being called twice. (rounded_login_button bug). Can't believe that that was it all the time..

firebase is not creating the user (user.uid = null)

I am trying to implemente facebook signin in flutter, however, firebase does not create a 'uid'. Doesn't the firebase create a uid automatically?
it returns:
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
below is the sign in method:
Future<UserCredential> signInWithFacebook(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if(result.status == LoginStatus.success) {
final OAuthCredential credential = FacebookAuthProvider.credential(result.accessToken.token);
return await FirebaseAuth.instance.signInWithCredential(credential)
.then((user) async {
final graphResponse = await http.get(Uri.parse(
'https://graph.facebook.com/v2.12/me?
fields=name,picture,email&access_token=${result
.accessToken.token}'));
final Map profile = jsonDecode(graphResponse.body);
if (profile != null){
authService.createUser(name: name, email: email, dob: dob, sex: sex);
}
return user;
});
}
Navigator.pushReplacement(context, MaterialPageRoute(
builder: (context) => Profile()));
return null;
}
The sign in method returns a facebook alert dialog requesting the permission to share email, when press continue red screen with the error appears. why is the firestore not creating the user? Thanks! I am not familiar with the system and just learning.
create user method in authServices:
Future<bool> createUser(
{String name,
User user,
String email,
String password,
String phone,
String sex,
String dob}) async {
var res = await firebaseAuth.createUserWithEmailAndPassword(
email: '$email',
password: '$password',
);
if ((res.user != null)) {
await saveUserToFirestore(name, res.user, email, dob, phone, sex);
return true;
} else {
return false;
}
}
As far as I can understand your code you first login the user with Facebook and then again create a new user with createUserWithEmailAndPassword. If you use the same email for both the second one will fail and give you null.
To track the auth state for all providers use the onAuthStateChanged listener:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
More about it here.

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.

With Flutter, how can I display Firebase Auth error messages caught within an AuthService class within a separate log in page widget?

I have a Registration Page with my sign up form and then an AuthService class I call to sign up the user, returning a mapped custom User class. I can check if the result of the function call is not null and therefore navigate my user to the home page, but I can't work out how to setState or similar to actually show the user the Firebase Auth messages in my Registration page, as the try/catch block is within my auth service class.
This is my abbreviated Registration screen widget:
class RegistrationScreen extends StatefulWidget {
#override
_RegistrationScreenState createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
//abbreviated...
RoundedButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
dynamic result = await _auth.registerWithEmailAndPassword(email, password, displayName);
if (result != null) {
Navigator.pushNamedAndRemoveUntil(context, Home.id, (_) => false);
}
}
}
}
}
The registerWithEmailAndPassword is within an imported AuthService class auth.dart:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final _firestore = Firestore.instance;
//create User object
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid, displayName: user.displayName) : null;
}
//auth change user stream
Stream<User> get user {
return _auth.onAuthStateChanged
.map(_userFromFirebaseUser);
}
// register
Future registerWithEmailAndPassword(String email, String password, String displayName) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch(e) {
print(e);
}
}
}
If I then test this with a badly formatted email, I correctly print to the console:
flutter: PlatformException(ERROR_INVALID_EMAIL, The email address is badly formatted., null)
However how can I use that PlatformException to setState or similar within my registration screen to show the e.message to the user?
Thanks.
You can create a class like this;
class Errors {
   static String show(String errorCode) {
     switch (errorCode) {
       case 'ERROR_EMAIL_ALREADY_IN_USE':
         return "This e-mail address is already in use, please use a different e-mail address.";
       case 'ERROR_INVALID_EMAIL':
         return "The email address is badly formatted.";
       case 'ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL':
         return "The e-mail address in your Facebook account has been registered in the system before. Please login by trying other methods with this e-mail address.";
       case 'ERROR_WRONG_PASSWORD':
         return "E-mail address or password is incorrect.";
       default:
         return "An error has occurred";
     }
   }
}
And then, when you get PlatformException error, you can show an alert dialog to user like this;
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch(e) {
print(Errors.show(e.code)); // On this line, call your class and show the error message.
}

Resources