Authentication in Flutter | User Interface Design
Tags: flutter, firebase, blog, tutorial
Published: 2022-05-05 22:19:00

Create a dynamic Input widget,  a form that can transform into both sign-in and registration based on the authentication mode, animate the transition between registration and sign-in form, and validate user input.


## Introduction

Hello and welcome to Khadka's Coding Lounge. This is Nibesh from Khadka's Coding Lounge. First of all, a special thanks to all the followers and subscribers of this blog series. So, far in this series, we made [Splash Screen](https://dev.to/kcl/create-splash-screen-and-launch-icon-in-flutter-1kdg), [User Onboarding System](https://dev.to/kcl/onboarding-with-go-router-in-flutter-2jd6), [Global Theme](https://dev.to/kcl/how-to-define-flutter-theme-c26), and [Custom widgets](https://dev.to/kcl/how-to-create-custom-widgets-app-bar-drawer-and-bottom-navigation-bar-in-flutter-4kkl) like the app bar, bottom navigation bar, and a drawer for our application. 

Today's blog is one of the most important parts of this blog series as you can tell from the title because now, according to our user-flow screen given below, we have to authenticate the user.


![App Launch Flow](https://cdn.hashnode.com/res/hashnode/image/upload/v1648547852788/hnFH5u_OH.png)

![Authentication User FLow](https://cdn.hashnode.com/res/hashnode/image/upload/v1648547835181/XQz4jAVRj.png)

Authentication is a basic yet very important aspect of any application regardless of platform. Serverless/Headless apps are on trend these days. Among them, Google's Firebase is one of the popular ones, especially for mobile applications. In this chapter of the series, we'll create an authentication UI. We'll create a dynamic input form widget to be more efficient. We'll also write some validations for the input, and animate sign in <-> registration form transitions.

You can find code up until from [here](https://github.com/nibukdk/astha-tutorial/tree/master/part_four).

## Create Authentication Form In Flutter
We're going to make a simple form. For __registration__, we'll have four inputs: email, username, password, and confirm password inputs, while the __sign-in__ form only uses two inputs: email and password. 

Let's create relevant files and folders in our project. 

```
# cursor on root folder
# create folders
mkdir lib/screens/auth lib/screens/auth/widgets lib/screens/auth/providers  lib/screens/auth/utils

# Create files
touch lib/screens/auth/auth_screen.dart   lib/screens/auth/providers/auth_provider.dart lib/screens/auth/widgets/text_from_widget.dart  lib/screens/auth/widgets/auth_form_widget.dart lib/screens/auth/utils/auth_validators.dart lib/screens/auth/utils/auth_utils.dart
``` 

### Create Dynamic Text Form Widget

Before we create our dynamic [text form field](https://api.flutter.dev/flutter/material/TextFormField-class.html) widget, let's go over the details of how dynamic it's going to be. 



![Auth Form Dynamic](https://cdn.hashnode.com/res/hashnode/image/upload/v1648601388688/JfnnBoI-u.png)

We'll make our dynamic text form widget in __text_from_widget.dart__ file.

```
import 'package:flutter/material.dart';

class DynamicInputWidget extends StatelessWidget {
  const DynamicInputWidget(
      {required this.controller,
      required this.obscureText,
      required this.focusNode,
      required this.toggleObscureText,
      required this.validator,
      required this.prefIcon,
      required this.labelText,
      required this.textInputAction,
      required this.isNonPasswordField,
      Key? key})
      : super(key: key);

  // bool to check if the text field is for password or not
  final bool isNonPasswordField;
  // Controller for the text field
  final TextEditingController controller;
  // Functio to toggle Text obscuractio on password text field
  final VoidCallback? toggleObscureText;
  // to obscure text or not bool
  final bool obscureText;
  // FocusNode for input
  final FocusNode focusNode;
  // Validator function
  final String? Function(String?)? validator;
  // Prefix icon for input form
  final Icon prefIcon;
  // label for input form
  final String labelText;
  // The keyword action to display
  final TextInputAction textInputAction;

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      decoration: InputDecoration(
        // Input with border outlined
        border: const OutlineInputBorder(
          // Make border edge circular
          borderRadius: BorderRadius.all(Radius.circular(10.0)),
        ),
        label: Text(labelText),
        prefixIcon: prefIcon,
        suffixIcon: IconButton(
          onPressed: toggleObscureText,
          // If is non-password filed like emal the suffix icon will be null
          icon: isNonPasswordField
              ? const Icon(null)
              : obscureText
                  ? const Icon(Icons.visibility)
                  : const Icon(Icons.visibility_off),
        ),
      ),
      focusNode: focusNode,
      textInputAction: textInputAction,
      obscureText: obscureText,
      validator: validator,
      // onSaved: passwordVlidator,
    );
  }
}
``` 
Let's go over a few important fields of our dynamic input widget class.

``` 

// bool to check if the text field is for password or not
  final bool isNonPasswordField; //# 1
   // Function to toggle Text obscuraction on password text field
  final VoidCallback? toggleObscureText; //# 2
  // to obscure text or not bool
  final bool obscureText;
   // Validator function 
  final String? Function(String?)? validator; //# 3
  
``` 
1. We are checking if the input type is a password or not. We're going to need that to determine whether to display a suffix icon that toggles the visibility of the password.
2. Boolean obscure text will change the password's visibility from asterisks to strings and vice-versa.
3. Validator is a function of a property [validator](https://api.flutter.dev/flutter/widgets/FormField/validator.html) in TextFormField. It returns null when valid and a custom string when invalid. 

### Input Validation in Flutter

Now, that our dynamic input is ready. Let's write some validators before we move on to create our form. We're created a separate file __auth_validators.dart__ for this sole purpose. We'll write three functions, which will separately validate  Email, Password, and Confirm Passwords for user input.

#### Create Email Validator in Flutter
```
class AuthValidators {
  // Create error messages to send.
 // #1
  static const String emailErrMsg =  "Invalid Email Address, Please provide a valid email.";
  static const String passwordErrMsg = "Password must have at least 6 characters.";
  static const String confirmPasswordErrMsg = "Two passwords don't match.";

// A simple email validator that  checks presence and position of @
// #2
  String? emailValidator(String? val) {
    final String email = val as String;

    // If length of email is <=3 then its invlaid
   // #3
    if (email.length <= 3) return emailErrMsg;

    // Check if it has @
  // # 4
    final hasAtSymbol = email.contains('@');

    // find position of @
    // # 5
    final indexOfAt = email.indexOf('@');

    // Check numbers of @
    // # 6
    final numbersOfAt = "@".allMatches(email).length;

    // Valid if has @
  // # 7
    if (!hasAtSymbol) return emailErrMsg;

    // and  if number of @ is only 1
  // # 8
    if (numbersOfAt != 1) return emailErrMsg;

    //and if  '@' is not the first or last character
  // # 9
    if (indexOfAt == 0 || indexOfAt == email.length - 1) return emailErrMsg;

    // Else its valid
    return null;
  }
}



``` 
Inside the auth_validators.dart, we create a __AuthValidators__ class. 
1. Then we created three error messages that'll be sent as output to the relevant error. 
2. Email Validator Function that takes input value from its input form.
3. Email is invalid if its length <=3.
4. (4&7) Checks if there's a *@* symbol and returns the error message on absence.
5. (5&9) Checks the position of *@*. If the position is in the beginning or at the end, that means the email is invalid.
6. (6&8) If the number of *@* is more or less than one, then the input is an invalid email.

That was a very simple validator, where we checked four things: if the input for email is empty, the length of the email input is less than 4,  the position and number @ in the input. 
 
*Reminder: Do not change the order of return statements. It will cause an error.*

####  Create Password Validator in Flutter

We'll continue with the second validator for the password. We'll only validate the password if it's not empty and its length is greater than 5.  So, once again inside AuthValidator class, we'll create a new function __passwordValidator__.


```
// Password validator
  String? passwordVlidator(String? val) {
    final String password = val as String;

    if (password.isEmpty || password.length <= 5) return passwordErrMsg;

    return null;
  }
``` 

#### Create Confirm Password Validator in Flutter

During registration, there will be two password input fields, the second one is to confirm the given password. Our __confirmPassword__ validator will take two inputs: the original password and the second password. 

```
// Confirm password
  String? confirmPasswordValidator(String? val, firstPasswordInpTxt) {
    final String firstPassword = firstPasswordInpTxt;
    final String secondPassword = val as String;
    // If either of the password field is empty
    // Or if thier length do not match then we don't need to compare their content
// #1
    if (firstPassword.isEmpty || 
        secondPassword.isEmpty ||
        firstPassword.length != secondPassword.length) {
      return confirmPasswordErrMsg;
    }

    //  If two passwords do not match then send error message
// #2
    if (firstPassword != secondPassword) return confirmPasswordErrMsg;

    return null;
  }
``` 
For password confirmation, we checked:
1. If either of the two passwords is empty or if their lengths don't match each other. If so, then return the error message.
2. The second condition compares the content of two passwords, if they don't match then returns the error message.

Like this our three validators are ready for action.

Altogether AuthValidators Class looks like this.

```
class AuthValidators {
  // Create error messages to send.
  static const String emailErrMsg =
      "Invalid Email Address, Please provide a valid email.";
  static const String passwordErrMsg =
      "Password must have at least 6 characters.";
  static const String confirmPasswordErrMsg = "Two passwords don't match.";

// A simple email validator that  checks presence and position of @
  String? emailValidator(String? val) {
    final String email = val as String;

    // If length of email is <=3 then its invlaid
    if (email.length <= 3) return emailErrMsg;

    // Check if it has @
    final hasAtSymbol = email.contains('@');
    // find position of @
    final indexOfAt = email.indexOf('@');
    // Check numbers of @
    final numbersOfAt = "@".allMatches(email).length;

    // Valid if has @
    if (!hasAtSymbol) return emailErrMsg;

    // and  if number of @ is only 1
    if (numbersOfAt != 1) return emailErrMsg;

    //and if  '@' is not first or last character
    if (indexOfAt == 0 || indexOfAt == email.length - 1) return emailErrMsg;

    // Else its valid
    return null;
  }

  // Password validator
  String? passwordVlidator(String? val) {
    final String password = val as String;

    if (password.isEmpty || password.length <= 5) return passwordErrMsg;

    return null;
  }

  // Confirm password
  String? confirmPasswordValidator(String? val, firstPasswordInpTxt) {
    final String firstPassword = firstPasswordInpTxt;
    final String secondPassword = val as String;
    // If either of the password field is empty
    // Or if thier length do not match then we don't need to compare their content
    if (firstPassword.isEmpty ||
        secondPassword.isEmpty ||
        firstPassword.length != secondPassword.length) {
      return confirmPasswordErrMsg;
    }

    //  If two passwords do not match then send error message
    if (firstPassword != secondPassword) return confirmPasswordErrMsg;

    return null;
  }
}

``` 


#### Homework on Validators

Here's something for you to practice.

1. __xyz..abc@mail.com__ is an invalid email address because symbols(_, ., -) should always be followed by letters or numbers. Can you write a validator for this error?.
2. On the password validator, check that it should contain both numbers and letters to be valid.
3. Add a username validator, check that it should not be the same as an email address. By the way, the username is the optional input.

## How to create Register & Sign in  Form in Flutter? 

### Auth Form Widget: Temporary 

Now, that we've made our dynamic input as well as validator it's time to put them together to create an authentication form visuals. We'll create our form widget in the __auth_form_widget.dart__ file which will be displayed in __auth_screen.dart__ file.

__auth_form_widget.dart__


```
import 'package:flutter/material.dart';

class AuthFormWidget extends StatefulWidget {
  const AuthFormWidget({Key? key}) : super(key: key);

  @override
  State<AuthFormWidget> createState() => _AuthFormWidgetState();
}

class _AuthFormWidgetState extends State<AuthFormWidget> {
  // Define Form key
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Form(
        key: _formKey,
        child: TextFormField(),
      ),
    );
  }
}

``` 
In flutter [Form](https://api.flutter.dev/flutter/widgets/Form-class.html) class can act as a container to display TextFormFields(if they are more than one). It also requires a [FormState](https://api.flutter.dev/flutter/widgets/FormState-class.html) which can be obtained via the global key of type FormState as we did just now. Before this form widget bulks up let's connect it to the auth_screen and display it on the app. We'll change it later on after connecting auth screen to the router. 

### Authentication Screen

__auth_screen.dart__


```
import 'package:flutter/material.dart';
import 'package:temple/globals/settings/router/utils/router_utils.dart';
import 'package:temple/screens/auth/widgets/auth_form_widget.dart';

class AuthScreen extends StatelessWidget {
  const AuthScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(APP_PAGE.auth.routePageTitle)),
      body:
      // Safe area prevents safe gards widgets to go beyond device edges
       SafeArea(
        //===========//
        // to dismiss keyword on tap outside use listener
        child: Listener(
          onPointerDown: (PointerDownEvent event) =>
              FocusManager.instance.primaryFocus?.unfocus(),
          //===========//
          child: SingleChildScrollView(
            child: SizedBox(
              width: double.infinity,
              child: Column(children: [
                // Display a welcome user image
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Image.asset(
                    'assets/AuthScreen/WelcomeScreenImage_landscape_2.png',
                    fit: BoxFit.fill,
                  ),
                ),
                const AuthFormWidget()
              ]),
            ),
          ),
        ),
      ),
    );
  }
}
``` 
Here is auth_screen.dart, this is it, we'll not make any changes in the future.

Since the app is only accessible to registered users, the authentication screen will be the first screen our user will be prompted to after onboarding. So, we don't need to display user_drawer or the bottom navbar. 

Dismissal of the active keyboard, when tapped outside, is an important characteristic to have. [Listener](https://api.flutter.dev/flutter/widgets/Listener-class.html) class responds to gesture events like a mouse click, tap, etc.  Together with [FocusManager](https://api.flutter.dev/flutter/widgets/FocusManager-class.html) we can track the focus node tree and unfocus the active keyboard. You might be wondering why am I using it on auth_screen as a whole instead of auth_form_widget. That's because gesture detector events should cover the whole visible area which in this case is [SingleChildScrollView](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html). 

Next, is the image section, if you're using the source code from the GitHub repo image should already be in the  AuthScreen folder inside assets. Let's add the path in the pubspec file.

```
assets:
    # Splash Screens
    - assets/splash/om_splash.png
    - assets/splash/om_lotus_splash.png
    - assets/splash/om_lotus_splash_1152x1152.png
    # Onboard Screens
    - assets/onboard/FindTemples.png
    - assets/onboard/FindVenues.png
// New line
    # Auth Screens
    - assets/AuthScreen/WelcomeScreenImage_landscape_2.png 
``` 
It's time to add auth_screen to app_router.dart

### Connect Auth Screen to Router

__app_router.dart__

```
 routes: [
        GoRoute(
          path: APP_PAGE.home.routePath,
          name: APP_PAGE.home.routeName,
          builder: (context, state) => const Home(),
        ),
        // Add the onboard Screen
        GoRoute(
            path: APP_PAGE.onboard.routePath,
            name: APP_PAGE.onboard.routeName,
            builder: (context, state) => const OnBoardScreen()),
        // New line from here
        // Add Auth Screen on Go Router
        GoRoute(
            path: APP_PAGE.auth.routePath,
            name: APP_PAGE.auth.routeName,
            builder: (context, state) => const AuthScreen()),
      ],

``` 
### Temporary Navigation To AuthScreen

We're yet to write a backend/business logic in this blog. But we do have to test UI. So, let's create a temporary link in our user_drawer.dart file that'll take us to auth_screen.
__user_drawer.dart__

```
...............
 actions: [
    
        ListTile(
            leading: Icon(
              Icons.person_outline_rounded,
              color: Theme.of(context).colorScheme.secondary,
            ),
            title: const Text('User Profile'),
            onTap: () {
              print("User Profile Button Pressed");
            }),
 // ============================//
    // A temporarry link to auth screen
        ListTile(
            leading: Icon(
              Icons.login,
              color: Theme.of(context).colorScheme.secondary,
            ),
            title: const Text('Register/Login'),
            onTap: () => GoRouter.of(context).goNamed(APP_PAGE.auth.routeName)),
 // ============================//
        ListTile(
            leading: Icon(
              Icons.logout,
              color: Theme.of(context).colorScheme.secondary,
            ),
            title: const Text('Logout'),
            onTap: () {
              print("Log Out Button Pressed");
            }),
      ],
.....
``` 
Save it all and run the app, navigate to auth_screen from the user drawer(press the person icon for the drawer), and you'll see auth screen. Now that we can see the authentication screen, let's create a full-fledged auth form.

### A Complete Auth Form Widget 

The codes for __auth_form_widget.dart__ file is really long. So, lets go over it few pieces at a time first. 

#### Instantiate AuthValidators inside _AuthFormWidgetState class.
```
  // Instantiate validator
  final AuthValidators authValidator = AuthValidators();

``` 
#### Create [controllers](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) and [focus nodes](https://api.flutter.dev/flutter/widgets/FocusNode-class.html).

```
// controllers 
  late TextEditingController emailController;
  late TextEditingController usernameController;
  late TextEditingController passwordController;
  late TextEditingController confirmPasswordController;

// create focus nodes
  late FocusNode emailFocusNode;
  late FocusNode usernameFocusNode;
  late FocusNode passwordFocusNode;
  late FocusNode confirmPasswordFocusNode;

``` 
#### Create obscure text bool

```
// to obscure text default value is false
  bool obscureText = true;
``` 
#### Auth Mode

Instead of creating two separate screens for registering and signing in, we'll just toggle between the few inputs displayed, on and off. For that, let's create a boolean to control authMode.

```
 // This will require toggling between register and signin mode
  bool registerAuthMode = false;
``` 

#### Instantiate and Dispose 
 
Instantiate all the *text editing controllers* and focus nodes on [initState](https://api.flutter.dev/flutter/widgets/State/initState.html) function. Similarly, these all also need to be disposed of once done so let's do that as well with the [dispose](https://api.flutter.dev/flutter/widgets/SingleTickerProviderStateMixin/dispose.html) method.

```
@override
  void initState() {
    super.initState();
    emailController = TextEditingController();
    usernameController = TextEditingController();
    passwordController = TextEditingController();
    confirmPasswordController = TextEditingController();

    emailFocusNode = FocusNode();
    usernameFocusNode = FocusNode();
    passwordFocusNode = FocusNode();
    confirmPasswordFocusNode = FocusNode();
  }

@override
  void dispose() {
    super.dispose();

    emailController.dispose();
    usernameController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();

    emailFocusNode.dispose();
    usernameFocusNode.dispose();
    passwordFocusNode.dispose();
    confirmPasswordFocusNode.dispose();
  }
```
#### Password Visibility Handler
 
Create a function that'll toggle the password's visibility on the relevant icon tap.
```
 void toggleObscureText() {
    setState(() {
      obscureText = !obscureText;
    });
  }
``` 
#### Snackbar Widget

Let's create a [snack bar](https://api.flutter.dev/flutter/material/SnackBar-class.html) to pop info on various circumstances.


```
// Create a scaffold messanger
  SnackBar msgPopUp(msg) {
    return SnackBar(
        content: Text(
      msg,
      textAlign: TextAlign.center,
    ));
  }
``` 
#### Prepare for List of Widgets In Column

Let's change the child of Form we're returning to a column. 
```
@override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Form(
        key: _formKey,
        child: Column(children: [],)
      ),
    );
  }
``` 
#### Input Widgets

It's time to create our email, username, password, and confirm password input forms. Inside column's children for from widget, we'll add inputs one by one.

##### Email Input
```
    // Email
            DynamicInputWidget(
              controller: emailController,
              obscureText: false,
              focusNode: emailFocusNode,
              toggleObscureText: null,
              validator: authValidator.emailValidator,
              prefIcon: const Icon(Icons.mail),
              labelText: "Enter Email Address",
              textInputAction: TextInputAction.next,
              isNonPasswordField: true,
            ),
            const SizedBox(
              height: 20,
            ),

``` 
*Make sure to import DynamicInput Widget*

##### Username Input

```
    // Username
              DynamicInputWidget(
                controller: usernameController,
                obscureText: false,
                focusNode: usernameFocusNode,
                toggleObscureText: null,
                validator: null,
                prefIcon: const Icon(Icons.person),
                labelText: "Enter Username(Optional)",
                textInputAction: TextInputAction.next,
                isNonPasswordField: true,
              ),
               const SizedBox(
                height: 20,
              ),

``` 
##### Password Input

```
// password
  DynamicInputWidget(
              controller: passwordController,
              labelText: "Enter Password",
              obscureText: obscureText,
              focusNode: passwordFocusNode,
              toggleObscureText: toggleObscureText,
              validator: authValidator.passwordVlidator,
              prefIcon: const Icon(Icons.password),
              textInputAction: registerAuthMode
                  ? TextInputAction.next
                  : TextInputAction.done,
              isNonPasswordField: false,
            ),

            const SizedBox(
              height: 20,
            ),

``` 

##### Confirm Password Input

```
// confirm password
DynamicInputWidget(
                controller: confirmPasswordController,
                focusNode: confirmPasswordFocusNode,
                isNonPasswordField: false,
                labelText: "Confirm Password",
                obscureText: obscureText,
                prefIcon: const Icon(Icons.password),
                textInputAction: TextInputAction.done,
                toggleObscureText: toggleObscureText,
                validator: (val) => authValidator.confirmPasswordValidator(
                    val, passwordController.text),
              ),

              const SizedBox(
                height: 20,
              ),
``` 


#### Regsiter/SignIn Button 

Later on, we'll also need to create and toggle functions to register and sign in once we set up the firebase back-end. For now, we'll just toggle the register/sign-in texts of the elevated button. 
```
// Toggle register/singin button text. Later on we'll also need to toggle register or signin function
Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton(
                  onPressed: () {},
                  child: const Text('Cancel'),
                ),
                const SizedBox(
                  width: 20,
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: Text(registerAuthMode ? 'Register' : 'Sign In'),
                  style: ButtonStyle(
                    elevation: MaterialStateProperty.all(8.0),
                  ),
                ),
              ],
            ),

``` 
#### Toggle Auth Mode  

Manage authentication mode with setState().
```
 Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(registerAuthMode
                    ? "Already Have an account?"
                    : "Don't have an account yet?"),
                TextButton(
                  onPressed: () =>
                      setState(() => registerAuthMode = !registerAuthMode),
                  child: Text(registerAuthMode ? "Sign In" : "Regsiter"),
                )
              ],
            )
``` 


#### Register/Sigin Animated Transition
 
If you save the file and run it. You probably notice the toggle doesn't work. That's because we haven't implemented animated transition yet. Two input widgets: __username__ and __confirm password__ widgets need to be hidden during the sign-in process but visible on registration. So, we'll use the toggle visibility base on the value of the variable *registerAuthMode* we created earlier. We'll use animation for a smooth transition during the toggle.

 [AnimatedContainer](https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html) class can be used for animation, while [AnimatedOpacity](https://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html) class can be used for fade in/out widgets.  With the combination of these two, we'll toggle input with animated opacity while the animated container will squeeze/fill the space occupied by input smoothly. 

So, let's animate the username, sized-box widget following it, and confirm-password input widget.


```
    // Username
              AnimatedContainer(
                duration: const Duration(milliseconds: 500),
                height: registerAuthMode ? 65 : 0,
                child: AnimatedOpacity(
                  duration: const Duration(milliseconds: 500),
                  opacity: registerAuthMode ? 1 : 0,
                  child: DynamicInputWidget(
                    controller: usernameController,
                    obscureText: false,
                    focusNode: usernameFocusNode,
                    toggleObscureText: null,
                    validator: null,
                    prefIcon: const Icon(Icons.person),
                    labelText: "Enter Username(Optional)",
                    textInputAction: TextInputAction.next,
                    isNonPasswordField: true,
                  ),
                ),
              ),
// We'll also need to fade in/out sizedbox
              AnimatedOpacity(
              duration: const Duration(milliseconds: 500),
              opacity: registerAuthMode ? 1 : 0,
              child: const SizedBox(
                height: 20,
              ),
            ),

// Confirm password 
 AnimatedContainer(
                duration: const Duration(milliseconds: 500),
                height: registerAuthMode ? 65 : 0,
                child: AnimatedOpacity(
                  duration: const Duration(milliseconds: 500),
                  opacity: registerAuthMode ? 1 : 0,
                  child: DynamicInputWidget(
                    controller: confirmPasswordController,
                    focusNode: confirmPasswordFocusNode,
                    isNonPasswordField: false,
                    labelText: "Confirm Password",
                    obscureText: obscureText,
                    prefIcon: const Icon(Icons.password),
                    textInputAction: TextInputAction.done,
                    toggleObscureText: toggleObscureText,
                    validator: (val) => authValidator.confirmPasswordValidator(
                        val, passwordController.text),
                  ),
                ),
              ),
            

``` 
After this, you'll be able to see two inputs on the authentication screen, cause sign-in mode is our default mode. Now, our Form Widget UI is ready. Don't be confused, you can find all __auth_form_widget.dart__ as a whole down below.


```
import 'package:flutter/material.dart';
import 'package:temple/screens/auth/utils/auth_validators.dart';
import 'package:temple/screens/auth/widgets/text_from_widget.dart';

class AuthFormWidget extends StatefulWidget {
  const AuthFormWidget({Key? key}) : super(key: key);

  @override
  State<AuthFormWidget> createState() => _AuthFormWidgetState();
}

class _AuthFormWidgetState extends State<AuthFormWidget> {
  // Define Form key
  final _formKey = GlobalKey<FormState>();

  // Instantiate validator
  final AuthValidators authValidator = AuthValidators();

// controllers
  late TextEditingController emailController;
  late TextEditingController usernameController;
  late TextEditingController passwordController;
  late TextEditingController confirmPasswordController;

// create focus nodes
  late FocusNode emailFocusNode;
  late FocusNode usernameFocusNode;
  late FocusNode passwordFocusNode;
  late FocusNode confirmPasswordFocusNode;

  // to obscure text default value is false
  bool obscureText = true;
  // This will require to toggle between register and sigin in mode
  bool registerAuthMode = false;

// Instantiate all the *text editing controllers* and focus nodes on *initState* function
  @override
  void initState() {
    super.initState();
    emailController = TextEditingController();
    usernameController = TextEditingController();
    passwordController = TextEditingController();
    confirmPasswordController = TextEditingController();

    emailFocusNode = FocusNode();
    usernameFocusNode = FocusNode();
    passwordFocusNode = FocusNode();
    confirmPasswordFocusNode = FocusNode();
  }

// These all need to be disposed of once done so let's do that as well.
  @override
  void dispose() {
    super.dispose();

    emailController.dispose();
    usernameController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();

    emailFocusNode.dispose();
    usernameFocusNode.dispose();
    passwordFocusNode.dispose();
    confirmPasswordFocusNode.dispose();
  }

// Create a function that'll toggle the password's visibility on the relevant icon tap.
  void toggleObscureText() {
    setState(() {
      obscureText = !obscureText;
    });
  }

// Let's create a snack bar to pop info on various circumstances.
// Create a scaffold messanger
  SnackBar msgPopUp(msg) {
    return SnackBar(
        content: Text(
      msg,
      textAlign: TextAlign.center,
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            // Email
            DynamicInputWidget(
              controller: emailController,
              obscureText: false,
              focusNode: emailFocusNode,
              toggleObscureText: null,
              validator: authValidator.emailValidator,
              prefIcon: const Icon(Icons.mail),
              labelText: "Enter Email Address",
              textInputAction: TextInputAction.next,
              isNonPasswordField: true,
            ),
            const SizedBox(
              height: 20,
            ),
            // Username
            AnimatedContainer(
              duration: const Duration(milliseconds: 500),
              height: registerAuthMode ? 65 : 0,
              child: AnimatedOpacity(
                duration: const Duration(milliseconds: 500),
                opacity: registerAuthMode ? 1 : 0,
                child: DynamicInputWidget(
                  controller: usernameController,
                  obscureText: false,
                  focusNode: usernameFocusNode,
                  toggleObscureText: null,
                  validator: null,
                  prefIcon: const Icon(Icons.person),
                  labelText: "Enter Username(Optional)",
                  textInputAction: TextInputAction.next,
                  isNonPasswordField: true,
                ),
              ),
            ),

            AnimatedOpacity(
              duration: const Duration(milliseconds: 500),
              opacity: registerAuthMode ? 1 : 0,
              child: const SizedBox(
                height: 20,
              ),
            ),

            DynamicInputWidget(
              controller: passwordController,
              labelText: "Enter Password",
              obscureText: obscureText,
              focusNode: passwordFocusNode,
              toggleObscureText: toggleObscureText,
              validator: authValidator.passwordVlidator,
              prefIcon: const Icon(Icons.password),
              textInputAction: registerAuthMode
                  ? TextInputAction.next
                  : TextInputAction.done,
              isNonPasswordField: false,
            ),

            const SizedBox(
              height: 20,
            ),

            AnimatedContainer(
              duration: const Duration(milliseconds: 500),
              height: registerAuthMode ? 65 : 0,
              child: AnimatedOpacity(
                duration: const Duration(milliseconds: 500),
                opacity: registerAuthMode ? 1 : 0,
                child: DynamicInputWidget(
                  controller: confirmPasswordController,
                  focusNode: confirmPasswordFocusNode,
                  isNonPasswordField: false,
                  labelText: "Confirm Password",
                  obscureText: obscureText,
                  prefIcon: const Icon(Icons.password),
                  textInputAction: TextInputAction.done,
                  toggleObscureText: toggleObscureText,
                  validator: (val) => authValidator.confirmPasswordValidator(
                      val, passwordController.text),
                ),
              ),
            ),
            const SizedBox(
              height: 20,
            ),

            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton(
                  onPressed: () {},
                  child: const Text('Cancel'),
                ),
                const SizedBox(
                  width: 20,
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: Text(registerAuthMode ? 'Register' : 'Sign In'),
                  style: ButtonStyle(
                    elevation: MaterialStateProperty.all(8.0),
                  ),
                ),
              ],
            ),

            const SizedBox(
              height: 20,
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(registerAuthMode
                    ? "Already Have an account?"
                    : "Don't have an account yet?"),
                TextButton(
                  onPressed: () =>
                      setState(() => registerAuthMode = !registerAuthMode),
                  child: Text(registerAuthMode ? "Sign In" : "Regsiter"),
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

``` 
Our validator's still not going to work, because we still haven't handled what to do on form submission. But let's check out the result of our hard work.

{% embed https://youtu.be/glba5xwAnHQ %}

 
![KCl](https://cdn.hashnode.com/res/hashnode/image/upload/v1649327360597/z11Ai6Jaf.png)

## Summary

Let's summarize what we did so far.

1. Created Dynamic Input Widget.
2. Wrote our personal validators for the inputs we'll be taking in.
3. Create both sign-in and registration forms on one screen.
4. Animated the transition between Registration and Sign In authentication. 

## Show Support

That's it for today. We'll work on Firebase Flutter Set-Up in the next section. If you have any questions then leave them in the comment section. 

Thank you for your time. Don't forget to give a like and share the article. Hopefully, you'll also subscribe to the publication to get notified of the next upload. This is Nibesh Khadka from Khadka's Coding Lounge. We are a freelancing agency that makes high-value websites and mobile applications. 

![Like, Share, Comment, and Subscribe](https://cdn.hashnode.com/res/hashnode/image/upload/v1648638943648/ki7sKHOlB.png)