Flutter - Designing Email Authentication System using Firebase - GeeksforGeeks

08 Dec, 2020
Flutter is an amazing tool for developing cross-platform applications using a single code base. While Flutter is useful, it gets even better when you add Firebase.  In this article we’ll discuss, how to implement the Email/Password Authentication process in Flutter, using Firebase.
In this article we’ll cover the following flutter development aspects:
Improved widget tree.
TextFormField Validation logic.
Toggle password text visibility.
Handling Authentication errors in UI.
Custom submit button with loading state.
UI logic and Firebase authentication logic, separated.
Note: Configure Firebase in your Flutter application, before diving into Firebase Authentication. Check this link for the initial firebase setup with flutter.
Now, Let’s look into the implementation.
Before any Firebase services can be used, you must first install the firebase_core plugin, which is responsible for connecting your application to Firebase. Add the plugin to your pubspec.yaml file. Also, add few supporting plugins which is used to deal with the authentication flow.
project/lib/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

firebase_core: "0.5.2"
firebase_auth: "^0.18.3"
provider: ^4.3.2+2
cloud_firestore: ^0.14.3
font_awesome_flutter: ^8.10.0
Install the plugin by running the following command from the project root:
$ flutter pub get
To initialize Firebase, call the .initializeApp() method on the Firebase class, as described in the below code. This method is asynchronous and returns a Future, so we need to ensure it has completed before displaying our main application. Updating the main( ) method of main.dart
project/lib/main.dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}
/libservices/authentication_service.dart
Create a new folder as services, and inside it a new dart file as authentication_service.dart, it will contain all the authentication logic, which in case help us to separate the UI logic.
authentication_service.dart
Dart
import 'package:cloud_firestore/cloud_firestore.dart';import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter_auth_example/models/user_model.dart';  class AuthenticationService {  final FirebaseAuth _firebaseAuth;  UserModel userModel = UserModel();  final userRef = Firestore.instance.collection("users");    AuthenticationService(this._firebaseAuth);    // managing the user state via stream.   // stream provides an immediate event of  // the user's current authentication state,  // and then provides subsequent events whenever  // the authentication state changes.  Stream<User> get authStateChanges => _firebaseAuth.authStateChanges();    //1  Future<String> signIn({String email, String password}) async {    try {      await _firebaseAuth.signInWithEmailAndPassword(          email: email, password: password);        return "Signed In";    } on FirebaseAuthException catch (e) {      if (e.code == 'user-not-found') {        return "No user found for that email.";      } else if (e.code == 'wrong-password') {        return "Wrong password provided for that user.";      } else {        return "Something Went Wrong.";      }    }  }    //2  Future<String> signUp({String email, String password}) async {    try {      await _firebaseAuth.createUserWithEmailAndPassword(          email: email, password: password);      return "Signed Up";    } on FirebaseAuthException catch (e) {      if (e.code == 'weak-password') {        return "The password provided is too weak.";      } else if (e.code == 'email-already-in-use') {        return "The account already exists for that email.";      } else {        return "Something Went Wrong.";      }    } catch (e) {      print(e);    }  }    //3  Future<void> addUserToDB(      {String uid, String username, String email, DateTime timestamp}) async {    userModel = UserModel(        uid: uid, username: username, email: email, timestamp: timestamp);      await userRef.document(uid).setData(userModel.toMap(userModel));  }    //4  Future<UserModel> getUserFromDB({String uid}) async {    final DocumentSnapshot doc = await userRef.document(uid).get();      //print(doc.data());      return UserModel.fromMap(doc.data());  }       //5  Future<void> signOut() async {    await _firebaseAuth.signOut();  }}
/libmodels/user_model.dart
Dart
import 'package:cloud_firestore/cloud_firestore.dart';import 'package:firebase_auth/firebase_auth.dart';  class UserModel {  String email;  String uid;  String username;  DateTime timestamp;    UserModel({this.email, this.uid, this.username, this.timestamp});    Map toMap(UserModel user) {    var data = Map<String, dynamic>();      data["uid"] = user.uid;    data["username"] = user.username;    data["email"] = user.email;    data["timestamp"] = user.timestamp;      return data;  }    UserModel.fromMap(Map<String, dynamic> mapData) {    this.uid = mapData["uid"];    this.username = mapData["username"];    this.email = mapData["email"];  }}
The user_model.dart is just a Plain Old Dart Object Strategy, which is used to convert the user model into a Map and also to retrieve it as a Map like it’s a JSON object basically.
Below are the methods used in the authentication_service.dart file:
This method accepts email and password as a parameter.
Email and Password are used to Sign-In the user.
It returns an appropriate message when the user “Signed In“.
It returns an appropriate message when FirebaseAuthException catches an error.
This method accepts email and password as a parameter.
Email and Password are used to Register the user.
It returns an appropriate message when the user “Signed Up” .
It returns an appropriate message when FirebaseAuthException catches an error.
This method accepts user uid, username, email, and the current timestamp( DateTime.now( ) ) of the user as a parameter.
Creating a new document of the current user in the Cloud Firestore Database. (Do enable cloud firestore from your firebase project console). The document name should be the uid of the user.
This method can only be triggered, when the current user is a new user, i.e the user registered in our application at the initial point.
This method accepts the current user uid as a parameter.
It will help us to get the current user, stored data from the cloud firestore database.
This method is simply used to signing out the user from the application.
Registering the AuthenticationService methods as Provider, in our main.dart file.
Project/lib/main.dart
Dart
import 'package:firebase_auth/firebase_auth.dart';import 'package:firebase_core/firebase_core.dart';import 'package:flutter/material.dart';import 'package:flutter_auth_example/pages/auth_screen_view.dart';import 'package:flutter_auth_example/pages/home_page.dart';import 'package:flutter_auth_example/services/authentication_service.dart';import 'package:provider/provider.dart';  Future<void> main() async {  WidgetsFlutterBinding.ensureInitialized();  await Firebase.initializeApp();  runApp(MyApp());}  class MyApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MultiProvider(      providers: [        Provider<AuthenticationService>(          create: (_) => AuthenticationService(FirebaseAuth.instance),        ),        StreamProvider(            create: (context) =>                context.read<AuthenticationService>().authStateChanges),      ],      child: MaterialApp(        theme: ThemeData(            brightness: Brightness.dark,            primaryColor: Colors.green[400],            accentColor: Colors.deepOrange[200]),        home: AuthenticationWrapper(),      ),    );  }}  class AuthenticationWrapper extends StatelessWidget {  @override  Widget build(BuildContext context) {    final firebaseUser = context.watch<User>();      if (firebaseUser != null) {      //If the user is successfully Logged-In.      return HomePage();    } else {      //If the user is not Logged-In.      return AuthScreenView();    }  }}
The AuthenticationWrapper class of main.dart, checks the state of the user. If the user is not Logged-In it will display the AuthScreenView( ), If the user is Logged-In it will display the HomePage( ).
/lib/pages/auth_screen_view.dart
 AuthScreenView is just a pageview, which deals with switching between the LoginPage() and RegisterPage() .
auth_screen_view.dart
Dart
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:flutter_auth_example/pages/login_page.dart';import 'package:flutter_auth_example/pages/register_page.dart';import 'package:font_awesome_flutter/font_awesome_flutter.dart';  class AuthScreenView extends StatefulWidget {  @override  _AuthScreenViewState createState() => _AuthScreenViewState();}  class _AuthScreenViewState extends State<AuthScreenView> {  PageController pageController;  int pageIndex = 0;    @override  void initState() {    // TODO: implement initState    super.initState();    pageController = PageController();  }    @override  void dispose() {    // TODO: implement dispose    super.dispose();    pageController.dispose();  }    onPageChanged(int pageIndex) {    setState(() {      this.pageIndex = pageIndex;    });  }    onTap(int pageIndex) {    //pageController.jumpToPage(pageIndex);    pageController.animateToPage(pageIndex,        duration: Duration(milliseconds: 300), curve: Curves.easeInOut);  }    @override  Widget build(BuildContext context) {    return Scaffold(      body: PageView(        children: [                      //when pageIndex == 0          LoginPage(),                       //when pageIndex == 1          RegisterPage()                  ],        controller: pageController,        onPageChanged: onPageChanged,      ),      bottomNavigationBar: CupertinoTabBar(        currentIndex: pageIndex,        onTap: onTap,        activeColor: Theme.of(context).primaryColor,        items: [          BottomNavigationBarItem(              title: Text("Log-In"),              icon: Icon(                FontAwesomeIcons.signInAlt,              )),          BottomNavigationBarItem(              title: Text("Register"),              icon: Icon(                FontAwesomeIcons.userPlus,              )),        ],      ),    );  }}
/lib/pages/register_page.dart
Dart
import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter/material.dart';import 'package:flutter_auth_example/services/authentication_service.dart';import 'package:provider/provider.dart';  class RegisterPage extends StatefulWidget {  @override  _RegisterPageState createState() => _RegisterPageState();}  class _RegisterPageState extends State<RegisterPage> {      //To Toggle Password Text Visibility.  bool _obscureText = true;   String _username, _email, _password;      //For the loading state.  bool _isSubmitting;     final _formKey = GlobalKey<FormState>();  final _scaffoldKey = GlobalKey<ScaffoldState>();    FirebaseAuth auth = FirebaseAuth.instance;  final DateTime timestamp = DateTime.now();    @override  Widget build(BuildContext context) {    return Scaffold(      key: _scaffoldKey,      appBar: AppBar(title: Text("GeeksForGeeks"), centerTitle: true),      body: Container(        padding: EdgeInsets.symmetric(horizontal: 20),        child: Center(          child: SingleChildScrollView(            child: Form(              key: _formKey,              child: Column(                children: [                  _showTitle(),                  _showUsernameInput(),                  _showEmailInput(),                  _showPasswordInput(),                  _showFormActions()                ],              ),            ),          ),        ),      ),    );  }    //1  _showTitle() {    return Text(      "Register",      style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),    );  }    //2  _showUsernameInput() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: TextFormField(        onSaved: (val) => _username = val,        validator: (val) => val.length < 6 ? "Username is too short." : null,        decoration: InputDecoration(            border: OutlineInputBorder(),            labelText: "Username",            hintText: "Enter Valid Username",            icon: Icon(              Icons.face,              color: Colors.grey,            )),      ),    );  }    //3  _showEmailInput() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: TextFormField(        onSaved: (val) => _email = val,        validator: (val) => !val.contains("@") ? "Invalid Email" : null,        decoration: InputDecoration(            border: OutlineInputBorder(),            labelText: "Email",            hintText: "Enter Valid Email",            icon: Icon(              Icons.mail,              color: Colors.grey,            )),      ),    );  }    //4  _showPasswordInput() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: TextFormField(        onSaved: (val) => _password = val,        validator: (val) => val.length < 6 ? "Password Is Too Short" : null,        obscureText: _obscureText,        decoration: InputDecoration(            suffixIcon: GestureDetector(              onTap: () {                setState(() {                  _obscureText = !_obscureText;                });              },              child:                  Icon(_obscureText ? Icons.visibility_off : Icons.visibility),            ),            border: OutlineInputBorder(),            labelText: "Password",            hintText: "Enter Valid Password",            icon: Icon(              Icons.lock,              color: Colors.grey,            )),      ),    );  }    //5  _showFormActions() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: Column(        children: [          _isSubmitting == true              ? CircularProgressIndicator(                  valueColor:                      AlwaysStoppedAnimation(Theme.of(context).primaryColor),                )              : RaisedButton(                  child: Text(                    "Submit",                    style: TextStyle(color: Colors.black, fontSize: 18),                  ),                  elevation: 8.0,                  shape: RoundedRectangleBorder(                      borderRadius: BorderRadius.all(Radius.circular(10))),                  color: Colors.orange,                  onPressed: _submit),        ],      ),    );  }    //6  _submit() {    final _form = _formKey.currentState;    if (_form.validate()) {      _form.save();      //print("Email $_email, Password $_password, Username $_username");      _registerUser();    } else {      print("Form is Invalid");    }  }      //7  _registerUser() async {    setState(() {      _isSubmitting = true;    });      final logMessage = await context        .read<AuthenticationService>()        .signUp(email: _email, password: _password);      logMessage == "Signed Up"        ? _showSuccessSnack(logMessage)        : _showErrorSnack(logMessage);      print(logMessage);      if (logMessage == "Signed Up") {      createUserInFirestore();    } else {      setState(() {        _isSubmitting = false;      });    }  }    //When User "Signed Up", success snack will display.  _showSuccessSnack(String message) {    final snackbar = SnackBar(      backgroundColor: Colors.black,      content: Text(        "$message",        style: TextStyle(color: Colors.green),      ),    );    _scaffoldKey.currentState.showSnackBar(snackbar);    _formKey.currentState.reset();  }    //When FirebaseAuth Catches error, error snack will display.  _showErrorSnack(String message) {    final snackbar = SnackBar(      backgroundColor: Colors.black,      content: Text(        "$message",        style: TextStyle(color: Colors.red),      ),    );    _scaffoldKey.currentState.showSnackBar(snackbar);  }    createUserInFirestore() async {    context.read<AuthenticationService>().addUserToDB(        uid: auth.currentUser.uid,        username: _username,        email: auth.currentUser.email,        timestamp: timestamp);  }}
Output:
Registration Page Authentication Logic Workflow.
The LoginPage is exactly similar to the RegisterPage, the only difference is, the LoginPage is having only two TextFormField (For email and password) and while submitting it triggers signIn() method of authentication_service.dart
/lib /pages/login_page.dart
Dart
import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter/material.dart';import 'package:flutter_auth_example/services/authentication_service.dart';import 'package:provider/provider.dart';  class LoginPage extends StatefulWidget {  @override  _LoginPageState createState() => _LoginPageState();}  class _LoginPageState extends State<LoginPage> {  bool _obscureText = true;  String _email, _password;  bool _isSubmitting;    final _formKey = GlobalKey<FormState>();  final _scaffoldKey = GlobalKey<ScaffoldState>();    FirebaseAuth auth = FirebaseAuth.instance;  final DateTime timestamp = DateTime.now();    @override  Widget build(BuildContext context) {    return Scaffold(      key: _scaffoldKey,      appBar: AppBar(        centerTitle: true,        title: Text("GeeksForGeeks"),      ),      body: Container(        padding: EdgeInsets.symmetric(horizontal: 20),        child: Center(          child: SingleChildScrollView(            child: Form(              key: _formKey,              child: Column(                children: [                  _showTitle(),                  _showEmailInput(),                  _showPasswordInput(),                  _showFormActions()                ],              ),            ),          ),        ),      ),    );  }    _showTitle() {    return Text(      "Login",      style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),    );  }    _showEmailInput() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: TextFormField(        onSaved: (val) => _email = val,        validator: (val) => !val.contains("@") ? "Invalid Email" : null,        decoration: InputDecoration(            border: OutlineInputBorder(),            labelText: "Email",            hintText: "Enter Valid Email",            icon: Icon(              Icons.mail,              color: Colors.grey,            )),      ),    );  }    _showPasswordInput() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: TextFormField(        onSaved: (val) => _password = val,        validator: (val) => val.length < 6 ? "Password Is Too Short" : null,        obscureText: _obscureText,        decoration: InputDecoration(            suffixIcon: GestureDetector(              onTap: () {                setState(() {                  _obscureText = !_obscureText;                });              },              child:                  Icon(_obscureText ? Icons.visibility_off : Icons.visibility),            ),            border: OutlineInputBorder(),            labelText: "Password",            hintText: "Enter Valid Password",            icon: Icon(              Icons.lock,              color: Colors.grey,            )),      ),    );  }    _showFormActions() {    return Padding(      padding: EdgeInsets.only(top: 20),      child: Column(        children: [          _isSubmitting == true              ? CircularProgressIndicator(                  valueColor:                      AlwaysStoppedAnimation(Theme.of(context).primaryColor),                )              : RaisedButton(                  child: Text(                    "Submit",                    style: TextStyle(color: Colors.black, fontSize: 18),                  ),                  elevation: 8.0,                  shape: RoundedRectangleBorder(                      borderRadius: BorderRadius.all(Radius.circular(10))),                  color: Colors.orange,                  onPressed: _submit),        ],      ),    );  }    _submit() {    final _form = _formKey.currentState;    if (_form.validate()) {      _form.save();      //print("Email $_email, Password $_password");      _LoginUser();    } else {      print("Form is Invalid");    }  }    _LoginUser() async {    setState(() {      _isSubmitting = true;    });      final logMessage = await context        .read<AuthenticationService>()        .signIn(email: _email, password: _password);      logMessage == "Signed In"        ? _showSuccessSnack(logMessage)        : _showErrorSnack(logMessage);      //print("I am logMessage $logMessage");      if (logMessage == "Signed In") {      return;    } else {      setState(() {        _isSubmitting = false;      });    }  }    _showSuccessSnack(String message) async {    final snackbar = SnackBar(      backgroundColor: Colors.black,      content: Text(        "$message",        style: TextStyle(color: Colors.green),      ),    );    _scaffoldKey.currentState.showSnackBar(snackbar);    _formKey.currentState.reset();  }    _showErrorSnack(String message) {    final snackbar = SnackBar(      backgroundColor: Colors.black,      content: Text(        "$message",        style: TextStyle(color: Colors.red),      ),    );    _scaffoldKey.currentState.showSnackBar(snackbar);    setState(() {      _isSubmitting = false;    });  }}
Output:
Login Page Authentication Logic Workflow.
The HomePage will be displayed when the firebaseUser != null, checking from main.dart
lib/pages/home_page.dart
Dart
import 'package:cloud_firestore/cloud_firestore.dart';import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter/material.dart';import 'package:flutter_auth_example/models/user_model.dart';import 'package:flutter_auth_example/services/authentication_service.dart';import 'package:provider/provider.dart';  class HomePage extends StatefulWidget {  @override  _HomePageState createState() => _HomePageState();}  class _HomePageState extends State<HomePage> {  FirebaseAuth auth = FirebaseAuth.instance;  final userRef = Firestore.instance.collection("users");  UserModel _currentUser;    String _uid;  String _username;  String _email;    @override  void initState() {    // TODO: implement initState    super.initState();    getCurrentUser();  }    getCurrentUser() async {    UserModel currentUser = await context        .read<AuthenticationService>()        .getUserFromDB(uid: auth.currentUser.uid);      _currentUser = currentUser;      print("${_currentUser.username}");      setState(() {      _uid = _currentUser.uid;      _username = _currentUser.username;      _email = _currentUser.email;    });  }    @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("HomePage"),        centerTitle: true,      ),      body: _currentUser == null          ? Center(child: CircularProgressIndicator())          : Column(              mainAxisAlignment: MainAxisAlignment.center,              children: [                Text(                  "uid is ${_uid} , email is ${_email}, name is ${_username}",                  textAlign: TextAlign.center,                ),                Center(                  child: RaisedButton(                    child: Text(                      "Logout",                      style: TextStyle(color: Colors.black, fontSize: 18),                    ),                    elevation: 8.0,                    shape: RoundedRectangleBorder(                        borderRadius: BorderRadius.all(Radius.circular(10))),                    color: Colors.orange,                    onPressed: () {                      context.read<AuthenticationService>().signOut();                    },                  ),                ),              ],            ),    );  }}
Output:
Home Page
android
Firebase
Technical Scripter 2020
Dart
Flutter
Technical Scripter
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
Flutter - Custom Bottom Navigation Bar
Flutter - Flexible Widget
ListView Class in Flutter
Flutter - Stack Widget
Android Studio Setup for Flutter Development
Flutter - Custom Bottom Navigation Bar
Flutter Tutorial
Flutter - Flexible Widget
Flutter - Stack Widget
Format Dates in Flutter