How to Create an Authentication Screen in Flutter

Flutter Jun 28, 2023

In this tutorial, we will learn how to create an authentication screen in Flutter. An authentication screen is an essential part of many mobile applications, as it allows users to sign in or register for an account. We will build a simple authentication screen with email and password fields, along with registration and login buttons.

This is how our authentication screen will look like:

flutter_authentication_screen_example

1. Setting up

For this tutorial we start with a new Flutter project, however, it is possible to follow along with an existing project. You can create a new project by executing flutter create flutter_authentication_screen where flutter_authentication_screen is the name of the project. Open the project and replace the contents of the main.dart file with the following code:

import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/screens/authentication_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Authentication Screen',
      home: AuthenticationScreen(),
    );
  }
}

In the above code, we created the MyApp widget which is the root of our application. The MaterialApp widget which is returned in the MyApp widget will make sure that our AuthenticationScreen is displayed.

2. Creating the AuthenticationScreen Widget

To begin creating the AuthenticationScreen widget, navigate to the lib directory in your project and create a new directory named screens. Inside the screens directory, create a file named authentication_screen.dart.

Inside the authentication_screen.dart file, we will define the AuthenticationScreen widget by extending a StatefulWidget.

import 'package:flutter/material.dart';

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

  @override
  State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}

class _AuthenticationScreenState extends State<AuthenticationScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Form(
            child: Column(
              children: [
                const SizedBox(height: 50),
                TextFormField(),
                TextFormField(),
                TextFormField(),
                ElevatedButton(
                  onPressed: () => {},
                  child: const Text('Register'),
                ),
                InkWell(
                  onTap: () => {},
                  child: const Text('Login instead'),
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

In the code snippet above, we have defined the AuthenticationScreen widget, which extends a StatefulWidget. Inside the build method, we have a Column widget that will hold our authentication UI components. The Column widget contains a Form widget, which contains a collection of TextFormField widgets for user input. Additionally, there is an ElevatedButton for registration and an InkWell widget to switch to login.

flutter_authentication_starter_ui_components

3. Creating the TextFormField widgets

Now, let us move on to finishing the TextFormField widgets. We will start with one TextFormField and then extract it into a separate widget to reuse it for the other fields.

import 'package:flutter/material.dart';

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

  @override
  State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}

class _AuthenticationScreenState extends State<AuthenticationScreen> {
  final emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Form(
            child: Column(
              children: [
                const SizedBox(height: 50),
                TextFormField(
                  controller: emailController,
                  decoration: InputDecoration(
                    floatingLabelStyle: const TextStyle(fontSize: 20),
                    icon: Icon(
                      Icons.email,
                      color: Theme.of(context).primaryColor,
                    ),
                    labelText: 'Email',
                  ),
                ),
                TextFormField(),
                TextFormField(),
                ElevatedButton(
                  onPressed: () => {},
                  child: const Text('Register'),
                ),
                InkWell(
                  onTap: () => {},
                  child: const Text('Login instead'),
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

In the above code, we have added the controller property, this property can be used to access the input of the user. Inside the decoration property we have added the InputDecoration widget. Inside the InputDecoration widget we defined the floatingLabelStyle to increase its size, an icon which is set to Icons.email and labelText which is set to Email.

flutter_authentication_email_text_form_field

Now we can extract our TextFormField to a separate widget to reuse the code. We will create a new directory inside our lib directory called widgets. In the widgets directory, we create the following file authentication_text_form_field.dart.

Inside the new file, we create the AuthenticationTextFormField widget:

import 'package:flutter/material.dart';

class AuthenticationTextFormField extends StatelessWidget {
  const AuthenticationTextFormField({
    Key? key,
    required this.icon,
    required this.label,
    required this.textEditingController,
  }) : super(key: key);

  final IconData icon;
  final String label;
  final TextEditingController textEditingController;

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: textEditingController,
      obscureText: label.toLowerCase().contains('password'),
      decoration: InputDecoration(
        floatingLabelStyle: const TextStyle(fontSize: 20),
        icon: Icon(
          icon,
          color: Theme.of(context).primaryColor,
        ),
        labelText: label,
      ),
    );
  }
}

In the above code, we have defined the AuthenticationTextFormField widget, which extends StatelessWidget. It takes the icon, label, and textEditingController as required parameters. The build method returns a TextFormField with the provided properties. We have also added the obscureText property, this property will make sure that the passwords are not visible on the screen.  

In our AuthenticationScreen we can now use our new widget to create the desired TextFormField widgets:

import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/widgets/authentication_text_form_field.dart';

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

  @override
  State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}

class _AuthenticationScreenState extends State<AuthenticationScreen> {
  final emailController = TextEditingController();
  final passwordController = TextEditingController();
  final passwordConfirmationController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Form(
            child: Column(
              children: [
                const SizedBox(height: 50),
                AuthenticationTextFormField(
                  icon: Icons.email,
                  label: 'Email',
                  textEditingController: emailController,
                ),
                AuthenticationTextFormField(
                  icon: Icons.vpn_key,
                  label: 'Password',
                  textEditingController: passwordController,
                ),
                AuthenticationTextFormField(
                  icon: Icons.password,
                  label: 'Password Confirmation',
                  textEditingController: passwordConfirmationController,
                ),
                ElevatedButton(
                  onPressed: () => {},
                  child: const Text('Register'),
                ),
                InkWell(
                  onTap: () => {},
                  child: const Text('Login instead'),
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

In the above code, we have created our three TextFormFields by using our AuthenticationTextFormField widget. The code becomes much easier to read and it is quite easy to add new TextFormFields.

flutter_authentication_email_password_and_password_confirmation_text_form_field_widgets

4. Styling the buttons

Now that our TextFormFields are finished we can move on to the buttons:

import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/widgets/authentication_text_form_field.dart';

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

  @override
  State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}

class _AuthenticationScreenState extends State<AuthenticationScreen> {
  final GlobalKey<FormState> _formKey = GlobalKey(); // Added globalKey
  final emailController = TextEditingController();
  final passwordController = TextEditingController();
  final passwordConfirmationController = TextEditingController();
  bool register = true; // Added register boolean

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Form(
            key: _formKey, // added _formKey
            child: Column(
              children: [
                const SizedBox(height: 50),
                AuthenticationTextFormField(
                  icon: Icons.email,
                  label: 'Email',
                  textEditingController: emailController,
                ),
                AuthenticationTextFormField(
                  icon: Icons.vpn_key,
                  label: 'Password',
                  textEditingController: passwordController,
                ),
                if (register == true) // Added check to disable field in login
                  AuthenticationTextFormField(
                    icon: Icons.password,
                    label: 'Password Confirmation',
                    textEditingController: passwordConfirmationController,
                  ),
                ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    minimumSize: const Size.fromHeight(50),
                    shape: const StadiumBorder(),
                  ),
                  onPressed: () => {},
                  child: Text(
                    register == true ? 'Register' : 'Login',
                    style: const TextStyle(fontSize: 17.5),
                  ),
                ),
                InkWell(
                  onTap: () => setState(() {
                    register = !register;
                    _formKey.currentState?.reset();
                  }),
                  child: Text(
                    register == true ? 'Login instead' : 'Register instead',
                  ),
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

In the provided code snippet, we created a GlobalKey<FormState> named _formKey to handle the form state. A boolean variable, register, was introduced to determine the current screen mode. We assigned _formKey to the key property of the Form widget, and applied styling to the ElevatedButton for a larger size and rounded corners. The button label now dynamically changes between "Register" and "Login" based on the register variable.

Additionally, we updated the onTap property of the InkWell widget to toggle the register variable and reset the form, while the text displayed by the InkWell widget also varies based on the register variable. At last, the AuthenticationTextFormField for the "Password Confirmation" field is now only visible during user registration.

flutter_authentication_switching_between_register_and_login_mode

5. Adding the wave

In the above example, we have a wave shape on top of our authentication screen. To implement this we want to create a new file named wave.dart inside our widgets directory. Inside this file we will place the following code:

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: WaveClipper(),
      child: Container(
        color: Theme.of(context).primaryColor,
        height: 200,
      ),
    );
  }
}

class WaveClipper extends CustomClipper<Path> {
  @override
  getClip(Size size) {
    var path = Path();
    path.lineTo(0, 175);

    // The values of the calculations would be path.quadraticBezierTo(100, 75, 200, 150) if the height is 200 and the width is 400;
    path.quadraticBezierTo(size.width * 0.25, size.height * 0.50 - 25,
        size.width * 0.50, size.height * 0.75);

    // The values of the calculations would be path.quadraticBezierTo(300, 225, 400, 150) if the height is 200 and the width is 400;
    path.quadraticBezierTo(
        size.width * 0.75, size.height + 25, size.width, size.height * 0.75);

    path.lineTo(size.width, 0);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(CustomClipper oldClipper) {
    return false;
  }
}

Normally I would go in-depth, but I want to leave this for another post, in the future I will update this post and add a link to the post. In short, the above code is clipping a regular Container widget to make sure that we get a wave.

Now let us add the Wave widget inside our AuthententicationScreen widget. First, we have to import it:

import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/widgets/authentication_text_form_field.dart';
import 'package:flutter_authentication_screen/widgets/wave.dart';

After importing we can add the Wave widget above our Form widget.

return Scaffold(
  body: Column(
    children: [
      const Wave(),
      Form(
        key: _formKey,
        child: Column( ... ),
      )
    ],
  ),
);

flutter_authentication_added_wave_on_top_of_the_screen

6. Applying padding

To improve the look and spacing of our widgets, we can add some padding to give them some breathing space and make them visually appealing. Let us make the following changes:

import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/widgets/authentication_text_form_field.dart';
import 'package:flutter_authentication_screen/widgets/wave.dart';

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

  @override
  State<AuthenticationScreen> createState() => _AuthenticationScreenState();
}

class _AuthenticationScreenState extends State<AuthenticationScreen> {
  final GlobalKey<FormState> _formKey = GlobalKey();
  final emailController = TextEditingController();
  final passwordController = TextEditingController();
  final passwordConfirmationController = TextEditingController();
  bool register = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          const Wave(),
          Form(
            key: _formKey,
            child: Padding( // Wrapped column with padding widget
              padding: const EdgeInsets.only(left: 25.0, right: 25.0),
              child: Column(
                children: [
                  const SizedBox(height: 25), // Reduced height from 50 to 25
                  AuthenticationTextFormField(
                    icon: Icons.email,
                    label: 'Email',
                    textEditingController: emailController,
                  ),
                  AuthenticationTextFormField(
                    icon: Icons.vpn_key,
                    label: 'Password',
                    textEditingController: passwordController,
                  ),
                  if (register == true)
                    AuthenticationTextFormField(
                      icon: Icons.password,
                      label: 'Password Confirmation',
                      textEditingController: passwordConfirmationController,
                    ),
                  const SizedBox(height: 25), // Added SizedBox
                  ElevatedButton(
                    style: ElevatedButton.styleFrom(
                      minimumSize: const Size.fromHeight(50),
                      shape: const StadiumBorder(),
                    ),
                    onPressed: () => {},
                    child: Text(
                      register == true ? 'Register' : 'Login',
                      style: const TextStyle(fontSize: 17.5),
                    ),
                  ),
                  const SizedBox(height: 20), // Added SizedBox
                  InkWell(
                    onTap: () => setState(() {
                      register = !register;
                      _formKey.currentState?.reset();
                    }),
                    child: Text(
                      register == true ? 'Login instead' : 'Register instead',
                    ),
                  )
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

In the above code snippet, we wrapped the second Column widget with a Padding widget to apply padding on the left and right of 25. Because we have added the Wave widget we reduced the padding from the first SizedBox widget from 50 to 25. And we added two additional SizedBox widgets to have padding around our buttons.

flutter_authentication_applied_padding_on_widgets

7. Changing the colors

At last, we want to change the color of our AuthenticationScreen. Because we used Theme.of(context), we only need to add a new theme inside our MaterialApp to change the colors:

import 'package:flutter/material.dart';
import 'package:flutter_authentication_screen/screens/authentication_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Authentication Screen',
      theme: ThemeData(
        primaryColor: Colors.blueAccent,
        colorScheme: ThemeData().colorScheme.copyWith(
          primary: Colors.blueAccent,
        ),
      ),
      home: const AuthenticationScreen(),
    );
  }
}

By changing the theme you will see that both our AuthenticationScreen adepts to it:

flutter_authentication_finished_authentication_screen_by_changing_the_color

8. Adding Validation

To further improve our authentication screen, the next logical step is to implement validation. Considering the length of this post, I have created a separate post dedicated to this step. Feel free to check it out:

How to add Validation to an Authentication Screen in Flutter
In web and mobile development, it is important to ensure the security and accuracy of user data. One way to do this is by validating the information users enter before proceeding. Validation helps us prevent errors and create a better user experience.

Conclusion

In this post, we learned how to create an authentication screen in Flutter. We started by setting up a new Flutter project and creating the basic structure of the authentication screen. We created the AuthenticationScreen widget and added the necessary UI components such as TextFormField widgets, buttons, and a toggle for registration and login.

We extracted the TextFormField widget into a separate reusable widget and added proper styling to the buttons. We also implemented a wave effect at the top of the authentication screen using a custom ClipPath widget.

Furthermore, we applied padding to the widgets to enhance the spacing and visual appeal. At last, we changed the colors of the authentication screen to customize its appearance.

By following this tutorial, you should now have a good understanding of how to create an authentication screen in Flutter. See the full source code.

Tags