Authentication Flow using Flutter and Firebase
Each one of your apps requires users to sign in and make a profile of Authentication Flow using Flutter. The need to let users sign up and make profiles is to let them purchase subscriptions and personalise the app according to their needs.
In this guide Authentication Flow using Flutter, we will explore how to let users sign up and sign in using Firebase with email and password. We will also cover the app’s UI part in this detailed guide. So, without wasting a single second, let’s get started.
Pre Requisite Authentication Flow using Flutter
- Flutter Project
- Firebase Setup in Flutter App
Let’s first design the login screen and signup screen Authentication Flow using Flutter. We have already added a custom button code in our previous walkthrough tutorial; you can view that for reference of the custom button code.
Adding Dependencies Authentication Flow using Flutter
We will use Firebase as a backend and Getx for state and route management. You can add those to your project by running the following command in your terminal Authentication Flow using Flutter flutter pub add firebase_core firebase_auth cloud_firestore get
UI Screen Design
We will first write the code and then explain it along the way so you will better understand what is happening Authentication Flow using Flutter.
Login Screen Code
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../controllers/auth_controller.dart';
import '../../core/constants/color_constants.dart';
import '../../core/constants/path_constants.dart';
import '../../core/routes/named_routes.dart';
import '../../core/theme/text_theme.dart';
import '../../widgets/custom_button.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
bool showPassword = false;
late TextEditingController emailController;
late TextEditingController passwordController;
@override
void initState() {
emailController = TextEditingController();
passwordController = TextEditingController();
super.initState();
}
@override
void dispose() {
emailController.dispose();
passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Image.asset(
PathConstants.logo,
width: Get.width * 0.25,
),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: Get.width * 0.06),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: Get.height * 0.04),
Center(
child: Image(
image: const AssetImage(PathConstants.loginScreenImage),
width: Get.width * 0.5,
alignment: Alignment.center,
),
),
SizedBox(height: Get.height * 0.04),
const Text(
'Sign In ',
style: AppTextTheme.bold24,
),
SizedBox(
height: Get.height * 0.025,
),
Text(
'Email Address',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.greyColor,
),
),
TextField(
controller: emailController,
cursorColor: ColorConstants.redColor,
style: AppTextTheme.regular16,
decoration: const InputDecoration(
hintText: 'johndoe@gmail.com',
prefixIcon: Icon(Icons.alternate_email),
),
),
SizedBox(
height: Get.height * 0.04,
),
Text(
'Password',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.greyColor,
),
),
TextField(
controller: passwordController,
obscureText: !showPassword,
cursorColor: ColorConstants.redColor,
style: AppTextTheme.regular16,
decoration: InputDecoration(
hintText: '*******',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: InkWell(
onTap: () {
setState(() {
showPassword = !showPassword;
});
},
child: Icon(
showPassword
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
),
),
),
SizedBox(
height: Get.height * 0.01,
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
Get.toNamed(NamedRoutes.forgotPassword);
},
child: Text(
'Forgot Password?',
style: AppTextTheme.regular14.copyWith(
decoration: TextDecoration.underline,
),
),
),
),
SizedBox(
height: Get.height * 0.025,
),
CustomButton(
color: ColorConstants.redColor,
onPressed: () {
if (!emailController.text.isEmail ||
passwordController.text.isEmpty) {
Get.snackbar(
'Error',
'Please enter valid email and password',
backgroundColor: ColorConstants.redColor,
colorText: Colors.white,
);
return;
}
Get.find<AuthController>().login(
emailController.text,
passwordController.text,
);
},
width: Get.width,
height: Get.height * 0.06,
radius: 16,
child: const Text(
'Sign In',
style: AppTextTheme.bold18,
),
),
SizedBox(
height: Get.height * 0.02,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'I \'m a new user. ',
style: AppTextTheme.regular14,
),
InkWell(
onTap: () {
Get.offNamed(NamedRoutes.signup);
},
child: Text(
'Sign Up',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.redColor,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
),
),
),
],
),
],
),
),
),
);
}
}
Login Screen Code Explanation
This class holds the mutable data that can change over the lifetime of the widget. In this case, it includes a boolean showPassword and two TextEditingController objects, emailController and passwordController. These controllers are used to read text input and listen for changes Authentication Flow using Flutter.
The initState method initializes the emailController and passwordController. The dispose method is then used to clean up the controllers when the State object is removed Authentication Flow using Flutter. The body of the Scaffold is a Padding widget that contains a SingleChildScrollView with a Column of widgets.
These widgets include images, text fields for email and password input, a custom button for submitting the form, and links for navigation to the forgot password and sign-up screens.The email and password TextField widgets use the emailController and passwordController respectively.
The password field also has an InkWell widget as a suffix icon, which toggles the visibility of the password. The custom button has an onPressed function that validates the email and password fields before calling the login method of the AuthController Authentication Flow using Flutter.
If the validation fails, a snack bar message is displayed. Finally, there’s a row of text at the bottom of the screen that includes a link to the sign up screen. Now, let’s move to the signup screen.
Signup Screen Code
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../controllers/auth_controller.dart';
import '../../core/constants/color_constants.dart';
import '../../core/constants/path_constants.dart';
import '../../core/routes/named_routes.dart';
import '../../core/theme/text_theme.dart';
import '../../widgets/custom_button.dart';
class SignupScreen extends StatefulWidget {
const SignupScreen({super.key});
@override
State<SignupScreen> createState() => _SignupScreenState();
}
class _SignupScreenState extends State<SignupScreen> {
final authController = Get.find<AuthController>();
late TextEditingController emailController;
late TextEditingController passwordController;
late TextEditingController confirmPasswordController;
bool showPassword = false;
bool showConfirmPassword = false;
@override
void initState() {
emailController = TextEditingController();
passwordController = TextEditingController();
confirmPasswordController = TextEditingController();
super.initState();
}
@override
void dispose() {
emailController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Image.asset(
PathConstants.logo,
width: Get.width * 0.25,
),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: Get.width * 0.06),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: Get.height * 0.06),
const Text(
'Sign Up ',
style: AppTextTheme.bold24,
),
SizedBox(
height: Get.height * 0.025,
),
Text(
'Email Address',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.greyColor,
),
),
TextField(
cursorColor: ColorConstants.redColor,
controller: emailController,
style: AppTextTheme.regular16,
decoration: const InputDecoration(
hintText: 'johndoe@gmail.com',
prefixIcon: Icon(Icons.alternate_email),
),
),
SizedBox(
height: Get.height * 0.04,
),
Text(
'Password',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.greyColor,
),
),
TextField(
controller: passwordController,
obscureText: !showPassword,
cursorColor: ColorConstants.redColor,
style: AppTextTheme.regular16,
decoration: InputDecoration(
hintText: '*******',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: InkWell(
onTap: () {
setState(() {
showPassword = !showPassword;
});
},
child: Icon(
showPassword
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
),
),
),
SizedBox(
height: Get.height * 0.04,
),
Text(
'Confirm Password',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.greyColor,
),
),
TextField(
controller: confirmPasswordController,
obscureText: !showConfirmPassword,
cursorColor: ColorConstants.redColor,
style: AppTextTheme.regular16,
decoration: InputDecoration(
hintText: '*******',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: InkWell(
onTap: () {
setState(() {
showConfirmPassword = !showConfirmPassword;
});
},
child: Icon(
showConfirmPassword
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
),
),
),
),
SizedBox(
height: Get.height * 0.04,
),
CustomButton(
color: ColorConstants.redColor,
onPressed: () {
if (!GetUtils.isEmail(emailController.text)) {
Get.snackbar(
'Error',
'Please enter a valid email address',
backgroundColor: ColorConstants.redColor,
colorText: ColorConstants.whiteColor,
);
return;
}
if (GetUtils.isNumericOnly(passwordController.text) ||
GetUtils.isAlphabetOnly(passwordController.text) ||
passwordController.text.length < 6) {
Get.snackbar(
'Error',
'Please enter alphanumeric password of at least 6
characters',
backgroundColor: ColorConstants.redColor,
colorText: ColorConstants.whiteColor,
);
return;
}
if (passwordController.text !=
confirmPasswordController.text) {
Get.snackbar(
'Error',
'Passwords do not match',
backgroundColor: ColorConstants.redColor,
colorText: ColorConstants.whiteColor,
);
return;
}
authController.signup(
emailController.text, passwordController.text);
},
width: Get.width,
height: Get.height * 0.06,
radius: 16,
child: const Text(
'Sign Up',
style: AppTextTheme.bold18,
),
),
SizedBox(
height: Get.height * 0.02,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Already have an account. ',
style: AppTextTheme.regular14,
),
InkWell(
onTap: () {
Get.offNamed(NamedRoutes.login);
},
child: Text(
'Sign In',
style: AppTextTheme.regular14.copyWith(
color: ColorConstants.redColor,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
),
),
),
],
),
],
),
),
),
);
}
}
Signup Screen Code Explanation
The signup screen is similar to a login screen that validates the input fields Authentication Flow using Flutter. If the validation fails, a snack bar message is displayed with the error. If the validation passes the flutter forums, the signup method of the authController is called with the email and password as arguments.
Authentication Class Implementation
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
import '../core/constants/color_constants.dart';
import '../core/routes/named_routes.dart';
import '../widgets/loader.dart';
class AuthController extends GetxController {
Future<void> signup(String email, String password) async {
try {
Get.dialog(const Loader(), barrierDismissible: false);
await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
Get.offNamed(NamedRoutes.home);
} catch (e) {
Get.back();
Get.snackbar(
'Error',
'Error while signing up',
backgroundColor: ColorConstants.redColor,
colorText: ColorConstants.whiteColor,
);
}
}
Future<void> login(String email, String password) async {
try {
Get.dialog(const Loader(), barrierDismissible: false);
await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
Get.offNamed(NamedRoutes.home);
} catch (e) {
Get.back();
Get.snackbar(
'Error',
'Error while signing in',
backgroundColor: ColorConstants.redColor,
colorText: ColorConstants.whiteColor,
);
}
}
}
The AuthController class extens GetxController, which is a part of the GetX package in Dart. AuthController is being used to handle user authentication tasks such as signing up and logging in.
The login function is an asynchronous function that takes in two parameters email and password. It uses these parameters to authenticate the user with Authentication Flow using Flutter Firebase .
Here’s a step-by-step breakdown of what happens in the login function Authentication Flow using Flutter:
- A loading dialog is shown using Get.dialog.
- The function attempts to sign in the user using Firebase’s
signInWithEmailAndPassword method. - If the sign-in is successful, the user is navigated to the home page using
Get.offNamed(NamedRoutes.home). - If an error occurs during the sign-in process, the loading dialog is dismissed
using Get.back(), and a snackbar with an error message is shown using
Get.snackbar.
Now Authentication Flow using Flutter, The signup function is very similar to the login function. It’s also an asynchronous function that takes in email and password as parameters. Here’s a step-by-step breakdown of what happens in the signup function:
- A loading dialog is shown using Get.dialog.
- The function attempts to create a new user using Firebase’s
createUserWithEmailAndPassword method. - If the user creation is successful, the user is navigated to the home page
using Get.offNamed(NamedRoutes.home). - If an error occurs during the user creation process, the loading dialog is
dismissed using Get.back(), and a snackbar with an error message is shown
using Get.snackbar.
In both functions Authentication Flow using Flutter, the try-catch block is used to handle any errors that might occur during the sign-in or sign-up process. This is a good practice as it helps prevent the app from crashing due to unhandled exceptions.
Conclusion
In this article, we cover user sign up and sign in flow in a Flutter app using Firebase Authentication and GetX for state management. It covers the UI design for the login and signup screens and also explains the implementation of the AuthController class that handles user authentication tasks.