Open In App

Flutter – Designing Email Authentication System using Firebase

Last Updated : 08 Dec, 2020
Improve
Improve
Like Article
Like
Save
Share
Report

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.

Project Preview

 

Now, Let’s look into the implementation.

Step 1: Installing and Initializing Firebase

Installation:

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

Initializing Firebase

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());
}

Step 2: Firebase Authentication Service

 Authentication Process:

/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.

The authentication_service.dart file:

Below are the methods used in the authentication_service.dart file:

1.   signIn({String email, String password})

  • 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.

2.  signUp({String email, String password})

  • 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.

3.   addUserToDB({String uid, String username, String email, DateTime timestamp})

  • 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.

4.  getUserFromDB({String uid})

  • 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.

5. signOut()

  • This method is simply used to signing out the user from the application.

Step 4 : Checking the Authentication State

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( ).

 

Step 5: Switching Between  ScreenView’s, Based On The Authentication State.

AuthScreenView( ):

/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,
              )),
        ],
      ),
    );
  }
}


 RegisterPage( ):

/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:

Flutter & Firebase: Email/Password Registration & Sign-in (Register Page)

Registration Page Authentication Logic Workflow.

LoginPage( ):

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.

 

 HomePage:

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

Final Output:



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads