I am coding an app for my company and I tried to add firebase authentication for login and registration to my app. The app shows no error and runs successfully.
But when a user tries to login with the wrong email and password, it is showing an internal flutter error instead of the toast I have programmed. And also I have used shared preferences to make users stay logged in.
So when a user tried to log in with the wrong credential it is showing an internal flutter error and when the app is re-opened, instead of going to the login screen, it is using the wrong credential and navigates user to Home Screen which is ridiculous.
These are the declared variables:
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final _formKey = GlobalKey<FormState>();
TextEditingController _emailcontroller = TextEditingController();
TextEditingController _passwordcontroller = TextEditingController();
bool passvis = true;
bool loading = false;
And this is the function for login:
Future loginForm() async {
FormState formSate = _formKey.currentState;
if (formSate.validate()) {
final User firebaseUser = (await firebaseAuth
.signInWithEmailAndPassword(
email: _emailcontroller.text,
password: _passwordcontroller.text)
.catchError((errMsg) {
displayToast("Error: " + errMsg.toString(), context);
}))
.user;
if (firebaseUser != null) {
setState(() {
loading = true;
});
usersRef.child(firebaseUser.uid).once().then((DataSnapshot snap) {
if (snap.value != null) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return LocationHome();
}));
displayToast("Succesfully LoggedIn!", context);
} else {
firebaseAuth.signOut();
displayToast("No user found! Please try SignUp", context);
}
});
} else {
displayToast("Error Occured! Cannot log you in", context);
}
}
}
}
And for Registration the code is below:
Future validateForm() async {
FormState formSate = _formKey.currentState;
if (formSate.validate()) {
final User firebaseUser = (await firebaseAuth
.createUserWithEmailAndPassword(
email: _emailcontroller.text,
password: _passwordcontroller.text)
.catchError((errMsg) {
displayToast("Error: " + errMsg.toString(), context);
}))
.user;
if (firebaseUser != null) {
Map userDataMap = {
"name": _namecontroller.text.trim(),
"email": _emailcontroller.text.trim(),
"phone": _phonecontroller.text.trim(),
};
usersRef.child(firebaseUser.uid).set(userDataMap);
displayToast("Succesfully Registered!", context);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return LocationHome();
}));
} else {
displayToast("User was unable to create", context);
}
}
}
}
The main.dart file is also coded correctly:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
SharedPreferences preferences = await SharedPreferences.getInstance();
var circle = preferences.getString("circle");
runApp(MaterialApp(
title: 'TaakStore',
home: circle == null ? Login() : Home(),
));
}
DatabaseReference usersRef =
FirebaseDatabase.instance.reference().child("users");
Dont worry about the displayToast function. It is a function manually created with flutter toast.
To display a toast, try the following:
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _emailcontroller.text,
password: _passwordcontroller.text
);
} on FirebaseAuthException catch (e) {
displayToast("Error: " + e.message.toString(), context);
print(e.message);
}
To check if the user is logged in or not use the following:
FirebaseAuth.instance
.authStateChanges()
.listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
authStateChanges() is of type Stream<User> which will listen for any changes on the state of a user. So if user is logged in, it will return a valid user object and you can navigate to the home screen. Therefore no need to use shared preferences.
To display a toast
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _emailcontroller.text,
password: _passwordcontroller.text
);
} on FirebaseAuthException catch (e) {
displayToast("Error: " + e.message.toString(), context);
print(e.message);
}
To check if the user is logged in
//inside the main.dart in the "MaterialApp" widget
MaterialApp(home:buildHome(),)
buildHome(){return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
print(snapshot);
//if the user is logged in return what you want
return "";
} else {
//else return what you want also
return"";
}
},
);}
Related
Firebase
Authentication: email/Password was already set to Enabled.
Realtime Database: users records exist.
Email and Password: thoroughly checked and verified correct.
below are the code for this issue;
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
void loginAndAuthenticateUser(BuildContext context) async
{
final User? firebaseUser = (await _firebaseAuth
.signInWithEmailAndPassword(
email: emailTextEditingController.text,
password: passwordTextEditingController.text
).catchError((errMsg){
displayToastMessage("Error: " + errMsg.toString(), context);
})).user;
if(firebaseUser !=null)
{
userRef.child(firebaseUser.uid).once().then((value) => (DataSnapshot snap){
if(snap.value != null){
Navigator.pushNamedAndRemoveUntil(context, MainScreen.idScreen, (route) => false);
displayToastMessage("Login successful", context);
}else{
_firebaseAuth.signOut();
displayToastMessage("No records exist. Please create new account", context);
}
});
}else{
displayToastMessage("Error: Cannot be signed in", context);
}
}
Try to use await with catch/try, instead of mixing with the .then/.catchError syntax (I don't know what's exactly in userRef, depending on it the database query might need to be adjusted):
try {
UserCredential userCredential = await _firebaseAuth
.signInWithEmailAndPassword(
email: emailTextEditingController.text,
password: passwordTextEditingController.text);
final User? firebaseUser = userCredential.user;
if (firebaseUser != null) {
final DatabaseEvent event = await
userRef.child(firebaseUser.uid).once();
if (event.snapshot.value != null) {
Navigator.pushNamedAndRemoveUntil(context, MainScreen.idScreen,
(route) => false);
displayToastMessage("Login successful", context);
} else {
displayToastMessage("No records exist. Please create new account",
context);
await _firebaseAuth.signOut();
}
} else {
displayToastMessage("Error: Cannot be signed in", context);
}
} catch(e) {
// handle error
}
I want to show an alert dialog when there is an error in the firebase auth.
Firebase already prints the error in the UI but i want to show a dialog to the user.
Heres my createUser and signInUser Funtion and my signup button function
Future registerWithEmailAndPassword({String email,password,username,image,phoneNumber}) async {
try {
UserCredential userCredential = await _firebaseAuth
.createUserWithEmailAndPassword(
email: email,
password: password,
);
} 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);
}
}
Future signInWithEmailAndPassword({String email, String password}) async {
try {
UserCredential userCredential = await _firebaseAuth
.signInWithEmailAndPassword(
email: email,
password: password
);
User user = userCredential.user;
assert(user.uid != null);
email = user.email;
} 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.');
}
}
}
press: () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
context
.read<Authentication>()
.signInWithEmailAndPassword(
email: emailController.text,
password: passwordController.text)
.whenComplete(() => Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
HomeScreen())));
}
},
You can set up an AlertDialog widget similar to this. The Yes/No buttons are probably overkill in your situation and if so, just convert to an ok button and then you don't have to check the return result.
Future<String> showYesNoAlertDialog({
#required BuildContext context,
#required String titleText,
#required String messageText,
}) async {
// set up the buttons
final Widget yesButton = FlatButton(
onPressed: () => Navigator.pop(context, 'yes'),
child: const Text('Yes'),
);
final Widget noButton = FlatButton(
onPressed: () => Navigator.pop(context, 'no'),
child: const Text('No'),
);
// set up the AlertDialog
final alert = AlertDialog(
title: Text(titleText),
content: Text(messageText),
actions: [
yesButton,
noButton,
],
);
// show the dialog
return showDialog(
context: context,
builder: (context) => alert,
);
}
Then where you have your print statements outputting the errors, you'd call the above widget like this
final dr = await showYesNoAlertDialog(
context: context,
titleText: 'Authentication Error',
messageText:
'There has been an error during authentication. Would you like to retry?',
);
if (dr == 'yes') {
// Yes button clicked
}
else {
// No button clicked
}
I have been learning how does flutter work with firestore and now I am working in user auth with password, email and username, when a user is created the email and password are saved with an uid but the username and the email(again) are saved in firestore with a different uid, by the way I have tried a lot of things to make it have the same id but I currently cant find the way. in addition to this, there is also a function that is supposed to edit the username and save those changes. The problem comes when trying to implement the edit functinality because the edit form doesnt return anything as an output except the loading screen, I think this error is happening because of the uids. How can I fix this problem?
models/user.dart
class CustomUser {
final String uid;
CustomUser({this.uid});
}
class UserData {
final String uid;
final String name;
UserData({this.uid, this.name});
}
models/username.dart
class Username {
final String name;
Username({this.name});
}
services/auth.dart
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create user obj based on fb user
CustomUser _userFromFirebaseUser(User user) {
return user != null ? CustomUser(uid: user.uid) : null;
}
Stream<CustomUser> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
//signin email password
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
User user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
//signup
Future registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
User user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
//signout
Future signOut() async {
try {
return await _auth.signOut();
} catch (e) {
print(e.toString());
return null;
}
}
services/database.dart
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection = FirebaseFirestore.instance.collection('usernames');
Future updateUserData(String name) async { // this is the function that has to edit the username
return await userCollection.doc(uid).set({
'name': name,
});
}
Future uploadUserInfo(userMap) async { // this function adds username and email to firestore
return await userCollection.doc(uid).set(userMap);
}
List<Username> _usernameListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return Username(
name: doc.data()['name'] ?? '',
);
}).toList();
}
// userData from snapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot) {
return UserData(
uid: uid,
name: snapshot.data()['name'],
);
}
Stream<List<Username>> get usernames {
return userCollection.snapshots().map(_usernameListFromSnapshot);
}
Stream<UserData> get userData {
return userCollection.doc(uid).snapshots().map(_userDataFromSnapshot);
}
}
register.dart(code that registers the user with a username)
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() => loading = true);
dynamic result = await _auth.registerWithEmailAndPassword(email, password).then((val) {
Map<String, String> userInfoMap = {
"name": name,
"email": email,
};
databaseService.uploadUserInfo(userInfoMap);
});
if (result == null) {
setState(() {
error = 'please suply a valid email';
loading = false;
});
}
}
}),
editForm.dart
final _formKey = GlobalKey<FormState>();
String _currentName;
final user = Provider.of<CustomUser>(context);
StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData userData = snapshot.data;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
Text('edit username!'),
SizedBox(
height: 30,
),
TextFormField(
// initialValue: userData.user gives a initial text to the input
validator: (val) => val.isEmpty ? 'Please enter a name' : null,
onChanged: (val) => setState(() => _currentName = val),
),
RaisedButton(
child: Text('Save'),
onPressed: () async {
if (_formKey.currentState.validate()) {
print('update if good');
await DatabaseService(uid: user.uid).updateUserData(
_currentName ?? userData.name,
);
}
Navigator.pop(context);
})
],
));
} else {
return Loading();
}
},
);
If you have any questions please let me know;)
In your register.dart, the registerWithEmailAndPassword method returns a User object which contains the uid internally created by FirebaseAuth however, it doesn't seem like you took used this uid to update your Firestore user document. I've implemented a sample of what should have been done below.
dynamic result = await _auth.registerWithEmailAndPassword(email, password).then((val) {
Map<String, String> userInfoMap = {
"name": name,
"email": email,
};
DatabaseService(uid: val.uid).uploadUserInfo(userInfoMap);
});
I just realized that your registerWithEmailAndPassword function returns a CustomUser instead of a Firebase User. I just modified it to make it work.
//signup
Future<User> registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
return result.user;
} catch (e) {
print(e.toString());
return null;
}
}
//editFrom.dart
//form validation function
Map<String, String> userMap = {'name': currentName};
await DatabaseService(uid: user.uid).uploadUserInfo(userMap);
Side note: when working with Futures, it helps if you specify the expected return type as this will help you with debugging. I've done it for the function above
I'm new in flutter and dealing with backend. I faced problem in signing out and I don't know how to fix it due to lack of experience. When I sign out, it would not return to the login screen (Authenticate). I'm not quite sure if the problem lies at the function I wrote at the wrapper or somewhere in auth.
The wrapper widget will be executed everytime the app runs:
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: new MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.redAccent,
),
home: Wrapper(),
),
);
}
}
The wrapper code is as below:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print(user);
if (user == null) {
return Authenticate();
} else {
return MyBottomNavigationBar();
}
}
}
Sign out button function:
onPressed: () async {
await _auth.signOut();
print('signing out');
},
Auth code:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mudah_replica/models/user.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
//Create user object based on FirebaseUser
User _userFromFirebaseUser(FirebaseUser user){
return user != null ? User(uid: user.uid) : null;
}
//Auth change user stream
Stream<User> get user {
return _auth.onAuthStateChanged
//.map((FirebaseUser user) => _userFromFirebaseUser(user));
.map(_userFromFirebaseUser);
}
//Sign in anonymously
Future signInAnon() async {
try {
AuthResult result = await _auth.signInAnonymously();
FirebaseUser user = result.user;
return user;
} catch(e) {
print(e.toString());
return null;
}
}
//Sign in with Email & Password
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch(e){
print(e.toString());
return null;
}
}
//Register with Email & Password
Future registerWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch(e){
print(e.toString());
return null;
}
}
//Sign out
Future signOut() async {
try{
return await _auth.signOut();
} catch(e){
print('Fail to sign out');
print(e.toString());
return null;
}
}
}
in your wrapper class, you are returning auth widget instead of navigate to the right screen, so it doesn't route. Instead, you might wanna navigate to the Auth screen. Do something like this:
onPressed: () async {
await _auth.signOut();
Navigator.push(context,
MaterialPageRoute(builder: (context) => Authenticate())
},
i get navigate to the home page even i entered the wrong credentials. i get the error that 'no user fond' but how to stop it.
signIn() async {
if (formkey.currentState.validate()) {
formkey.currentState.save();
try {
_auth.signInWithEmailAndPassword(email: email, password: password);
Navigator.push(context, MaterialPageRoute(builder: (context)=> Home()));
} catch (e) {
print(e.message);
}
}
}
It looks like you're not awaiting for any response from auth.signInWithEmailAndPassword which is what you'd need to do in order to check and see if they're actually authenticated.
So:
await _auth.signInWithEmailAndPassword(email: email, password: password);
When I run this code:
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(email: "nope#stackhack.com", password: "secret");
print("Signed in");
}
catch (e) {
print(e);
}
I get the error printed, and not the "Signed in" message. The error contains a code property, which has a value "ERROR_USER_NOT_FOUND". So to respond to this specific error, I could do something like:
if (e.code == "ERROR_USER_NOT_FOUND") {
print("User not found");
}
There are 2 solutions, one is
signIn() async {
if (formkey.currentState.validate()) {
formkey.currentState.save();
try {
FirebaseUser user = await _auth.signInWithEmailAndPassword(email: email, password: password);
if (user != null && await user.getIdToken() != null) {
final FirebaseUser currentUser = await auth.currentUser();
if (currentUser.uid == user.uid) {
print("Success in signing, you can now navigate");
Navigator.push(context, MaterialPageRoute(builder: (context) => Home()));
}
}
} catch (e) {
print(e.message);
}
}
}
other is (thanks to Frank)
signIn() async {
if (formkey.currentState.validate()) {
formkey.currentState.save();
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(email: "nope#stackhack.com", password: "secret");
print("Signed in, you may want to navigate now");
Navigator.push(context, MaterialPageRoute(builder: (context) => Home()));
}
catch (e) {
if (e.code == "ERROR_USER_NOT_FOUND") {
print("User not found");
}
}
}
}