Firebase Auth Exceptions handling | Flutter
Clear instruction messages on exceptions will save you from losing trusted users.
Pre-Reqs:
This guide expects you already know flutter basics, and if you don't, then here is a Flutter Learning Roadmap.
Background:
In this guide, I have modularized all the functionalities into different classes and enums with good practices to have maintainable and extendable code. All login and Result status handling logic is completely isolated from the UI.
Auth Result Status Enum:
Enums are very effective if you have multiple results statuses as in our case.
As the name suggests, AuthResultStatus will have the result status of firebase auth.
enum AuthResultStatus {
successful,
emailAlreadyExists,
wrongPassword,
invalidEmail,
userNotFound,
userDisabled,
operationNotAllowed,
tooManyRequests,
undefined,
}
Auth Exception Handler:
As the name suggests, the AuthExceptionHandler class handles the exception by reading the error code of the firebase exception object, returns the AuthResultStatus based on the error code plus also generates an error message for every AuthResultStatus.
class AuthExceptionHandler {
static handleException(e) {
print(e.code);
var status;
switch (e.code) {
case "ERROR_INVALID_EMAIL":
status = AuthResultStatus.invalidEmail;
break;
case "ERROR_WRONG_PASSWORD":
status = AuthResultStatus.wrongPassword;
break;
case "ERROR_USER_NOT_FOUND":
status = AuthResultStatus.userNotFound;
break;
case "ERROR_USER_DISABLED":
status = AuthResultStatus.userDisabled;
break;
case "ERROR_TOO_MANY_REQUESTS":
status = AuthResultStatus.tooManyRequests;
break;
case "ERROR_OPERATION_NOT_ALLOWED":
status = AuthResultStatus.operationNotAllowed;
break;
case "ERROR_EMAIL_ALREADY_IN_USE":
status = AuthResultStatus.emailAlreadyExists;
break;
default:
status = AuthResultStatus.undefined;
}
return status;
}
///
/// Accepts AuthExceptionHandler.errorType
///
static generateExceptionMessage(exceptionCode) {
String errorMessage;
switch (exceptionCode) {
case AuthResultStatus.invalidEmail:
errorMessage = "Your email address appears to be malformed.";
break;
case AuthResultStatus.wrongPassword:
errorMessage = "Your password is wrong.";
break;
case AuthResultStatus.userNotFound:
errorMessage = "User with this email doesn't exist.";
break;
case AuthResultStatus.userDisabled:
errorMessage = "User with this email has been disabled.";
break;
case AuthResultStatus.tooManyRequests:
errorMessage = "Too many requests. Try again later.";
break;
case AuthResultStatus.operationNotAllowed:
errorMessage = "Signing in with Email and Password is not enabled.";
break;
case AuthResultStatus.emailAlreadyExists:
errorMessage =
"The email has already been registered. Please login or reset your password.";
break;
default:
errorMessage = "An undefined Error happened.";
}
return errorMessage;
}
}
Firebase Auth Helper:
FirebaseAuthHelper class will handle the communication with Firebase Auth Service and just returns the result status to the UI. Through this class Authentication logic is isolated from the UI code.
class FirebaseAuthHelper {
final _auth = FirebaseAuth.instance;
AuthResultStatus _status;
///
/// Helper Functions
///
Future<void> createAccount({email, pass}) async {
try {
AuthResult authResult = await _auth.createUserWithEmailAndPassword(
email: email, password: pass);
if (authResult.user != null) {
_status = AuthResultStatus.successful;
} else {
_status = AuthResultStatus.undefined;
}
} catch (e) {
print('Exception @createAccount: $e');
_status = AuthExceptionHandler.handleException(e);
}
return _status;
}
Future<AuthResultStatus> login({email, pass}) async {
try {
final authResult =
await _auth.signInWithEmailAndPassword(email: email, password: pass);
if (authResult.user != null) {
_status = AuthResultStatus.successful;
} else {
_status = AuthResultStatus.undefined;
}
} catch (e) {
print('Exception @createAccount: $e');
_status = AuthExceptionHandler.handleException(e);
}
return _status;
}
logout() {
_auth.signOut();
}
}
Implementation:
Here is the final implementation for account creation and login. And based on the result status, either error dialog is shown or the user proceeds to the success page.
_createAccount() async {
final status = await FirebaseAuthHelper().createAccount(
email: email, pass: password);
if (status == AuthResultStatus.successful) {
// Navigate to success page
} else {
final errorMsg = AuthExceptionHandler.generateExceptionMessage(
status);
_showAlertDialog(errorMsg);
}
}
_login() async {
final status = await FirebaseAuthHelper().login(
email: email, pass: password);
if (status == AuthResultStatus.successful) {
// Navigate to success page
} else {
final errorMsg = AuthExceptionHandler.generateExceptionMessage(
status);
_showAlertDialog(errorMsg);
}
}
Complete Source on Github:
The complete GitHub project can be found here with a good UI plus some other best practices in the UI.
Looking forward to your thoughts, feedback for improvement.
This won’t work anymore, because the new FirebaseAuthException class throws different error codes.
change example: ERROR_WRONG_PASSWORD -> wrong-password
The new error codes can be found on this page - i didn’t found the list of these anywhere else, only in the javascript documentation…
Yes, they have soon updated most of firebase APIs and yes we need an update to this blog as well. Thank you for reminding.