Flutter FirebaseAuthException : Display custom error messages - flutter

I am struggling to to show my custom error messages during login/sign up. The error messages that are showing up are the default ones coming from Firebase. I need to display the custom error messages in the code below depending on the error code. Please assist...
I'm not sure what's still missing. Iv'e tried everything but still not winning
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import '../widgets/auth/auth_form.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({Key? key}) : super(key: key);
static const routeName = '/auth_screen';
#override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _auth = FirebaseAuth.instance;
var _isLoading = false;
void _submitAuthForm(
String email,
String password,
String displayName,
String phoneNumber,
bool isLogin,
BuildContext ctx,
) async {
UserCredential authResult;
try {
setState(() {
_isLoading = true;
});
if (isLogin) {
authResult = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} else {
authResult = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
await FirebaseFirestore.instance
.collection('userProfile')
.doc(authResult.user!.uid)
.set(
{
'displayName': displayName,
'email': email,
'phoneNumber': phoneNumber,
'uid': authResult.user!.uid,
},
);
}
} on FirebaseAuthException catch (error) {
var message = 'An error has occured.'; // default message
switch (error.code) {
case 'EMAIL_NOT_FOUND':
message = 'There is no user account with the email address provided.';
break;
case 'EMAIL_EXISTS':
message = 'The email address is already in use by another account.';
break;
case 'INVALID_PASSWORD':
message = 'Invalid password. Please enter correct password.';
break;
}
setState(() {
_isLoading = false;
});
showDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('An error occured'),
content: Text(
error.message.toString(),
),
actions: [
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
);
}
}

I think your problem lies in the fact that while you have defined a message variable, you are not using it inside the dialog. You are instead using error.message.toString().
showDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('An error occured'),
content: Text(
error.message.toString(), /// <-------------- HERE
),
actions: [
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
);
Replace that with message and it should work.

Related

How to fix eternal loading when fetching data in flutter?

I am trying to make sign in in dart using cubit/bloc.
And when I try to change state of my cubit(auth) to Waiting => CircularProgressIndicator
Then my app is getting stuck when api returns statusCode == 400 and I call another emit to show dialog window
But if sign in is successful(statusCode == 200) then everything is ok, and I navigate to main_page.dart
How to fix this problem and transfer data reponse from api to widget(I want to send statusCode and message to widget)
My cubit file:
class AuthCubit extends Cubit<AuthState> {
AuthCubit() : super(AuthState(
email: "",
password: "",
));
final ApiService _apiService = ApiService();
void setEmail(String email) => emit(state.copyWith(email: email));
void setPassword(String password) => emit(state.copyWith(password: password));
Future signIn() async {
emit(WaitingSignInAuth(state.email, state.password));
try {
final data = await _apiService.signIn(
email: state.email ?? "",
password: state.password ?? ""
);
if (data['success']) {
print(data);
emit(const SuccessAutentification());
}
} on DioError catch (ex) {
print(ex.toString());
//I want to transfer to ErrorSignInAuth ex.response!.data['message'] but I don't know how
emit(ErrorSignInAuth(state.email, state.password));
} catch (e) {
print(e);
}
}
}
This is my state file:
class AuthState extends Equatable {
final String? email;
final String? password;
const AuthState({
this.email,
this.password,
});
AuthState copyWith({
String? email,
String? password,
}) {
return AuthState(
email: email ?? this.email,
password: password ?? this.password,
);
}
#override
List<Object?> get props =>
[email, password];
}
class SuccessAutentification extends AuthState {
const SuccessAutentification() : super();
}
class WaitingSignInAuth extends AuthState {
const WaitingSignInAuth(email, password) : super(email: email, password: password);
}
class ErrorSignInAuth extends AuthState {
const ErrorSignInAuth(email, password) : super(email: email, password: password);
}
And this is the widget where I use this cubit:
#override
Widget build(BuildContext context) {
return BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state is WaitingSignInAuth) {
showDialog(
context: context,
builder: (context) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black.withOpacity(0.6),
child: const Center(
child: CircularProgressIndicator(
strokeWidth: 1,
color: Colors.black,
backgroundColor: Colors.white,
),
),
));
}
if (state is SuccessAutentification) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const MainWidget(),
),
);
}
if (state is ErrorAuthentification) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Bad request"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Error") // I want to show here error message
],
),
),
actions: <Widget>[
TextButton(
child: const Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
}
);
}
},
builder: (context, state) {
return Scaffold(
LoginButton(
color: _isValid ? AppColors.white : AppColors.whiteA3A3A3,
text: "Login in",
textColor: Colors.black,
isButtonDisabled: _isValid ? false : true,
onPressed: () {
if (_key.currentState!.validate()) {
BlocProvider.of<AuthCubit>(context).setEmail(_email.text.trim());
BlocProvider.of<AuthCubit>(context).setPassword(_password.text.trim());
BlocProvider.of<AuthCubit>(context).signIn();
_key.currentState!.save();
}
},
)
);
},
);
}
In signIn() function, after you emit WaitingSignInAuth state, in some cases you are not emitting any other state, so your page is always loading.
Future signIn() async {
emit(WaitingSignInAuth(state.email, state.password));
try {
final data = await _apiService.signIn(
email: state.email ?? "",
password: state.password ?? ""
);
if (data['success']) {
print(data);
emit(const SuccessAutentification());
} else {
// emit state
emit(ErrorSignInAuth(state.email, state.password));
}
} on DioError catch (ex) {
print(ex.toString());
emit(ErrorSignInAuth(state.email, state.password));
} catch (e) {
print(e);
// emit state
emit(ErrorSignInAuth(state.email, state.password));
}
}

Getx navigation not responding Flutter

I have been working on this Getx library for the first time and i don't know why but the initial screen function is not working for some reason. It's not showing any bug in the stack trace so i guess it's gotta be something else. has anyone any idea? Debug shows no error or warnings.
here's the code
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tutorial_two/screens/screen_home.dart';
import 'package:tutorial_two/screens/screen_login.dart';
import 'package:velocity_x/velocity_x.dart';
class AuthController extends GetxController {
static AuthController instance =
Get.find(); // auth controller instance; will be called in other widgets
// user instance
late Rx<User?> _user;
// get user data like name, email, password etc
FirebaseAuth auth = FirebaseAuth.instance; // firebase auth instance
#override
void onReady() {
super.onReady();
_user = Rx<User?>(auth.currentUser); // getting current user
_user.bindStream(
auth.userChanges(),
); //notifies app about user login and logout
ever(_user,
_initialScreen); //this funnction will make sure user gets to correct screen
}
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => const LoginScreen());
} else {
Get.offAll(() => const HomeScreen());
}
}
Future<void> register(
BuildContext context, String email, username, password) async {
try {
await auth.createUserWithEmailAndPassword(
email: email, password: password);
//await Future.delayed(const Duration(seconds: 2));
Future.delayed(
const Duration(
seconds: 1,
), () {
VxToast.show(context,
msg: "Registration Successful",
bgColor: Colors.green.shade100,
textColor: Colors.green.shade500,
textSize: 14,
position: VxToastPosition.center);
});
} catch (e) {
VxToast.show(context,
msg: "Error: $e",
bgColor: Colors.red.shade100,
textColor: Colors.red.shade500,
textSize: 14,
position: VxToastPosition.center);
}
}
Future<void> login(BuildContext context, String email, password) async {
try {
await auth.signInWithEmailAndPassword(email: email, password: password);
//await Future.delayed(const Duration(seconds: 2));
Future.delayed(
const Duration(
seconds: 1,
), () {
VxToast.show(context,
msg: "Login Successful",
bgColor: Colors.green.shade100,
textColor: Colors.green.shade500,
textSize: 14,
position: VxToastPosition.center);
});
} catch (e) {
VxToast.show(context,
msg: "Error: $e",
bgColor: Colors.red.shade100,
textColor: Colors.red.shade500,
textSize: 14,
position: VxToastPosition.center);
}
}
Future<void> logOut(BuildContext context) async {
try {
await auth.signOut();
} catch (e) {
VxToast.show(context,
msg: "Error: $e",
bgColor: Colors.red.shade100,
textColor: Colors.red.shade500,
textSize: 14,
position: VxToastPosition.center);
}
}
}
here's the main code
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tutorial_two/screens/screen_home.dart';
import 'package:tutorial_two/screens/screen_login.dart';
import 'package:tutorial_two/screens/screen_signup.dart';
import 'package:tutorial_two/screens/screen_splash.dart';
import 'package:tutorial_two/utils/auth_controller.dart';
import 'package:tutorial_two/utils/routes.dart';
import 'package:tutorial_two/widgets/themes.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
Firebase.initializeApp().then((value) {
Get.put(AuthController());
});
runApp(const TutorialTwo());
}
class TutorialTwo extends StatelessWidget {
const TutorialTwo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.system,
theme: MyTheme.lightTheme(context),
darkTheme: MyTheme.darkTheme(context),
debugShowCheckedModeBanner: false,
home: const SplashScreen(),
routes: {
MyRoutes.homeRoute: (context) => const HomeScreen(),
MyRoutes.loginRoute: (context) => const LoginScreen(),
MyRoutes.signupRoute: (context) => const SignupScreen(),
},
);
}
}
To use Get navigation management, you must use GetMaterialApp instead of MaterialApp,
//Replace the following line
return MaterialApp(
//with this one
return GetMaterialApp(

How to be automatically redirect after firebase login?

I followed this tutorial https://www.youtube.com/watch?v=oJ5Vrya3wCQ for implement firebase auth to my app.
All is working great except one thing. When i'm in the login screen and click on the login button, i have to restart the app to access the homepage.
That's doesn't happen when i click logout, i'm redirect to login instantly.
Here is my sign in:
Future<String> signIn({String email, String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
return "Connecté";
} on FirebaseAuthException catch (e) {
return e.message;
}
}
main.dart
class AuthenticationWrapper extends StatelessWidget {
const AuthenticationWrapper({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
if (firebaseUser != null) {
return HomePage();
}
return IntroScreen();
}
}
My elevated button:
ElevatedButton(
onPressed: () {
context.read<Authentication>().signIn(
email: emailController.text.trim(),
password: passwordController.text.trim());
},
child: Text('Se connecter'),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(
Color(0xff0d47a1)),
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40))),
))
Actually you have not added a navigator to the home screen after the user logs in.
In the Elevated button's onPressed
onPressed: () {
await context.read<Authentication>().signIn(
email: emailController.text.trim(),
password: passwordController.text.trim());
Navigator.push(context,MaterialPageRoute(builder: (context) => HomePage()));
},
This will work.
The sample code is
bool invalid = false;
String password;
String error = "";
loginUser() async {
print(error);
try {
await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
} on FirebaseAuthException catch (e) {
error = e.toString();
}
FirebaseAuth.instance.authStateChanges().listen((user) {
if (error.isNotEmpty) {
setState(() {
invalid = true;
});
} else {
setState(() {
Navigator.popAndPushNamed(context, "homePage");
});
}
});
}
And just above the login button
if (error.isNotEmpty)
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
if (error.split(" ").contains("password"))
Text(
"Invalid Password",
style: TextStyle(color: Colors.red),
),
if (error.split(" ").contains("record"))
Text(
"User Does Not Exist",
style: TextStyle(color: Colors.red),
),
]),

When signed in can display Name etc but only display 'null' if signed out. Not a message like 'Not signed in'

To understand using Google Sign In I am using a simple example to sign in and out. The idea is to display the username if signed in or display 'Not signed in' if signed out. If I sign in the username is being correctly displayed. If I sign out 'null' is being displayed instead of the message 'Not signed in'. The code:
String displayName =
_profile != null ? '${_profile['displayName'].toString()}' : 'Not
signed in';
is not assigning 'Not signed in' to displayName.
I have tried testing _profile and displayName for length and other options but there is something I am just not understanding.
main.dart
import 'package:flutter/material.dart';
import 'auth.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String displayName = 'Not Signed In';
Map<String, dynamic> _profile;
bool _loading = false;
#override
initState() {
super.initState();
authService.profile.listen((state) => setState(() => _profile = state));
authService.loading.listen((state) => setState(() => _loading = state));
authService.user.toString();
}
#override
Widget build(BuildContext context) {
displayName = _profile == null
? 'Not Signed In'
: '${_profile['displayName'].toString()}';
if (displayName.length < 1) displayName = 'Not Signed In';
return MaterialApp(
title: 'FlutterBase',
home: Scaffold(
appBar: AppBar(
title: Text('Flutterbase'),
backgroundColor: Colors.amber,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[LoginButton(), UserProfile()],
),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text(displayName), accountEmail: Text('gggg')),
ListTile(
title: Text('Item 1'),
)
],
),
)),
);
}
}
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Map<String, dynamic> _profile;
bool _loading = false;
#override
initState() {
super.initState();
authService.profile.listen((state) => setState(() => _profile = state));
authService.loading.listen((state) => setState(() => _loading = state));
authService.user.toString();
}
#override
Widget build(BuildContext context) {
String displayName = _profile != null
? '${_profile['displayName'].toString()}'
: 'Not signed in';
String email =
_profile != null ? _profile['email'].toString() : 'Not given';
return Column(children: <Widget>[
Container(padding: EdgeInsets.all(20), child: Text(_profile.toString())),
Container(padding: EdgeInsets.all(20), child: Text(displayName)),
Container(padding: EdgeInsets.all(20), child: Text(email)),
Container(
padding: EdgeInsets.all(20),
child: Text('Loading: ${_loading.toString()}')),
]);
}
}
class LoginButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: authService.user,
builder: (context, snapshot) {
if (snapshot.hasData) {
return MaterialButton(
onPressed: () => authService.signOut(),
color: Colors.red,
textColor: Colors.white,
child: Text('Signout'),
);
} else {
return MaterialButton(
onPressed: () => authService.googleSignIn(),
color: Colors.white,
textColor: Colors.black,
child: Text('Login with Google'),
);
}
});
}
}
auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:rxdart/rxdart.dart';
class AuthService {
String userName;
String userEmail;
String userUid;
final GoogleSignIn _googleSignIn = GoogleSignIn();
final FirebaseAuth _auth = FirebaseAuth.instance;
final Firestore _db = Firestore.instance;
Observable<FirebaseUser> user; //Firebase user
Observable<Map<String, dynamic>> profile; // custom user data in Firestore
PublishSubject loading = PublishSubject();
// constructor
AuthService() {
user = Observable(_auth.onAuthStateChanged);
profile = user.switchMap((FirebaseUser u) {
if (u != null) {
return _db
.collection('users')
.document(u.uid)
.snapshots()
.map((snap) => snap.data);
} else {
return Observable.just({});
}
});
}
Future<FirebaseUser> googleSignIn() async {
try {
loading.add(true);
GoogleSignInAccount googleUser = await _googleSignIn.signIn();
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
FirebaseUser user = await _auth.signInWithCredential(credential);
updateUserData(user);
print("signed in " + user.displayName);
loading.add(false);
//see if this works
userName = user.displayName;
userEmail = user.email;
userUid = user.uid;
return user;
} catch (error) {
return error;
}
}
void updateUserData(FirebaseUser user) async {
DocumentReference ref = _db.collection('users').document(user.uid);
return ref.setData({
'uid': user.uid,
'email': user.email,
'photoURL': user.photoUrl,
'displayName': user.displayName,
'lastSeen': DateTime.now()
}, merge: true); // makes not destructive update
}
Future<String> signOut() async {
try {
await _auth.signOut();
return 'SignOut';
} catch (e) {
return e.toString();
}
}
}
final AuthService authService = AuthService();
You are checking whether _profile is null, which is likely fine before the user has signed in, but I suspect _profile is not being set to null when the user is subsequently logged out.
I suspect this line of code return Observable.just({}); is the issue, which is returning an empty map instead of null, when the FirebaseUser is null.
You can try to return null there instead of an empty map, though I'm not sure that will work. Otherwise you could add an isNotEmpty check in addition to the null check eg
String displayName =
(_profile != null && _profile.isNotEmpty) ? '${_profile['displayName'].toString()}' : 'Not
signed in';
Hope that helps!

Flutter: I am unable to logout my app with google sign in library. How to do this?

I have tried below google sign in example , it is working fine. But when I moved _handleSignOut() function to another screen , it is not signing out. My requirement is after login success, my homepage is visible. On the top of homepage , there is a logout button. on the click of which , I want to logout my app with google.
import 'dart:async';
import 'dart:convert' show json;
import "package:http/http.dart" as http;
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: <String>[
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
void main() {
runApp(
MaterialApp(
title: 'Google Sign In',
home: SignInDemo(),
),
);
}
class SignInDemo extends StatefulWidget {
#override
State createState() => SignInDemoState();
}
class SignInDemoState extends State<SignInDemo> {
GoogleSignInAccount _currentUser;
String _contactText;
#override
void initState() {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
if (_currentUser != null) {
_handleGetContact();
}
});
_googleSignIn.signInSilently();
}
Future<void> _handleGetContact() async {
setState(() {
_contactText = "Loading contact info...";
});
final http.Response response = await http.get(
'https://people.googleapis.com/v1/people/me/connections'
'?requestMask.includeField=person.names',
headers: await _currentUser.authHeaders,
);
if (response.statusCode != 200) {
setState(() {
_contactText = "People API gave a ${response.statusCode} "
"response. Check logs for details.";
});
print('People API ${response.statusCode} response: ${response.body}');
return;
}
final Map<String, dynamic> data = json.decode(response.body);
final String namedContact = _pickFirstNamedContact(data);
setState(() {
if (namedContact != null) {
_contactText = "I see you know $namedContact!";
} else {
_contactText = "No contacts to display.";
}
});
}
String _pickFirstNamedContact(Map<String, dynamic> data) {
final List<dynamic> connections = data['connections'];
final Map<String, dynamic> contact = connections?.firstWhere(
(dynamic contact) => contact['names'] != null,
orElse: () => null,
);
if (contact != null) {
final Map<String, dynamic> name = contact['names'].firstWhere(
(dynamic name) => name['displayName'] != null,
orElse: () => null,
);
if (name != null) {
return name['displayName'];
}
}
return null;
}
Future<void> _handleSignIn() async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
}
}
Future<void> _handleSignOut() async {
_googleSignIn.disconnect();
}
Widget _buildBody() {
if (_currentUser != null) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ListTile(
leading: GoogleUserCircleAvatar(
identity: _currentUser,
),
title: Text(_currentUser.displayName),
subtitle: Text(_currentUser.email),
),
const Text("Signed in successfully."),
Text(_contactText),
RaisedButton(
child: const Text('SIGN OUT'),
onPressed: _handleSignOut,
),
RaisedButton(
child: const Text('REFRESH'),
onPressed: _handleGetContact,
),
],
);
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Text("You are not currently signed in."),
RaisedButton(
child: const Text('SIGN IN'),
onPressed: _handleSignIn,
),
],
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Google Sign In'),
),
body: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: _buildBody(),
));
}
}
Use signOut() instead. If you are using FirebaseAuth, you need to log out from both
Future<void> _handleSignOut() async {
await FirebaseAuth.instance.signOut();
await _googleSignIn.signOut();
}
I found solution for this. I was creating again an object of googlesignin in logout screen.
It was my mistake.
Using same object of googlesignin as declared above
GoogleSignIn _googleSignIn = GoogleSignIn(....) in Logout screen will work. I just need to call this object like _googleSignIn.signOut().