image

02 Dec 2025

9K

35K

In modern application development, a secure and intuitive login form is a fundamental requirement. It serves as the gateway to personalized user experiences, ensuring data privacy and controlled access. Flutter, Google's UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, provides robust widgets and tools to create such forms with elegance and efficiency.

This article will guide you through the process of building a professional login form in Flutter, covering essential UI components, state management, and basic input validation.

Project Setup

First, ensure you have Flutter installed and set up. Create a new Flutter project:


flutter create login_app
cd login_app

Open the lib/main.dart file. We'll start by defining our main application structure:


import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Login Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const LoginPage(),
    );
  }
}

Building the Login Page UI

Our login page will be a StatefulWidget to manage the state of input fields and loading indicators. We'll use TextEditingControllers to retrieve text from the input fields and a GlobalKey to manage the form state for validation.

Create a new file, e.g., lib/login_page.dart (or update main.dart directly for simplicity), and define the LoginPage widget and its state:


import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State createState() => _LoginPageState();
}

class _LoginPageState extends State {
  final _formKey = GlobalKey();
  final TextEditingController _usernameController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  bool _isLoading = false;
  String? _errorMessage;

  @override
  void dispose() {
    _usernameController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  Future _submitForm() async {
    if (_formKey.currentState!.validate()) {
      setState(() {
        _isLoading = true;
        _errorMessage = null;
      });

      // Simulate network request for authentication
      try {
        await Future.delayed(const Duration(seconds: 2)); // Simulate API call

        final String username = _usernameController.text;
        final String password = _passwordController.text;

        // Simple validation logic (replace with actual authentication service)
        if (username == 'user' && password == 'password') {
          // Login successful
          if (!mounted) return; // Check if the widget is still in the tree
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Login Successful!')),
          );
          // TODO: Navigate to the home screen or dashboard
        } else {
          // Login failed
          setState(() {
            _errorMessage = 'Invalid username or password.';
          });
        }
      } catch (e) {
        setState(() {
          _errorMessage = 'An unexpected error occurred. Please try again.';
        });
      } finally {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Login'),
      ),
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16.0),
          child: Form(
            key: _formKey,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // Logo or App Icon
                const Icon(
                  Icons.lock_person,
                  size: 100,
                  color: Colors.blueAccent,
                ),
                const SizedBox(height: 40),

                // Username/Email Field
                TextFormField(
                  controller: _usernameController,
                  decoration: const InputDecoration(
                    labelText: 'Username',
                    hintText: 'Enter your username or email',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.person),
                  ),
                  keyboardType: TextInputType.emailAddress,
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter your username or email';
                    }
                    // Optional: Basic email format validation
                    // if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
                    //   return 'Please enter a valid email address';
                    // }
                    return null;
                  },
                ),
                const SizedBox(height: 20),

                // Password Field
                TextFormField(
                  controller: _passwordController,
                  decoration: const InputDecoration(
                    labelText: 'Password',
                    hintText: 'Enter your password',
                    border: OutlineInputBorder(),
                    prefixIcon: Icon(Icons.lock),
                  ),
                  obscureText: true, // Hide password input
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter your password';
                    }
                    if (value.length < 6) {
                      return 'Password must be at least 6 characters long';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 20),

                // Error Message Display
                if (_errorMessage != null)
                  Padding(
                    padding: const EdgeInsets.only(bottom: 10.0),
                    child: Text(
                      _errorMessage!,
                      style: const TextStyle(color: Colors.red, fontSize: 14),
                      textAlign: TextAlign.center,
                    ),
                  ),

                // Login Button
                _isLoading
                    ? const CircularProgressIndicator() // Show loading indicator
                    : SizedBox(
                        width: double.infinity,
                        height: 50,
                        child: ElevatedButton(
                          onPressed: _submitForm,
                          style: ElevatedButton.styleFrom(
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(8),
                            ),
                          ),
                          child: const Text(
                            'Login',
                            style: TextStyle(fontSize: 18),
                          ),
                        ),
                      ),
                const SizedBox(height: 20),

                // Optional: Forgot Password, Sign Up links
                TextButton(
                  onPressed: () {
                    // TODO: Implement forgot password logic or navigation
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Forgot Password pressed!')),
                    );
                  },
                  child: const Text('Forgot Password?'),
                ),
                TextButton(
                  onPressed: () {
                    // TODO: Implement navigation to a sign-up page
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Sign Up pressed!')),
                    );
                  },
                  child: const Text('Don\'t have an account? Sign Up'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Explanation of Key Components

  • GlobalKey<FormState> _formKey: This key uniquely identifies our Form widget and allows us to interact with its state, particularly for calling the validate() method.
  • TextEditingController: Used to programmatically control and retrieve the text content of a TextFormField. It's crucial to dispose() these controllers in the dispose method of the State to prevent memory leaks.
  • TextFormField: The core input widget.
    • controller: Assigns a TextEditingController to manage its value.
    • decoration: Customizes the visual appearance, including labelText, hintText, border, and prefixIcon.
    • obscureText: true: Hides the text input, essential for password fields.
    • keyboardType: Specifies the keyboard layout (e.g., TextInputType.emailAddress for email fields).
    • validator: A function that receives the current field value and returns an error string if the input is invalid, or null if it's valid.
  • ElevatedButton: The submit button. Its onPressed callback triggers the _submitForm method.
  • Form Widget: It's crucial for grouping multiple TextFormField widgets and managing their collective validation state.
  • _isLoading and CircularProgressIndicator: A boolean flag _isLoading is used to manage the loading state. When a login attempt is in progress, the button is replaced with a CircularProgressIndicator to provide visual feedback to the user.
  • _errorMessage: A nullable string variable used to hold and display any login-related error messages directly below the input fields.
  • SingleChildScrollView: Wraps the entire form to ensure that the content remains scrollable, preventing pixel overflow issues on smaller screens or when the keyboard appears.

Form Validation

The validator property of TextFormField is a powerful feature for input validation. When _formKey.currentState!.validate() is called, each TextFormField's validator function is executed. If any validator returns a non-null string, it's displayed as an error message below the field, and validate() returns false.

Our example includes:

  • Checks for empty fields.
  • A minimum length check for the password (6 characters).

Login Logic (_submitForm)

The _submitForm method handles the submission process:

  1. It first calls _formKey.currentState!.validate(). If validation fails for any field, it stops processing.
  2. Sets _isLoading to true and clears any previous _errorMessage, triggering a UI update (showing the loading indicator).
  3. Simulates an asynchronous API call using Future.delayed. In a real application, this would be an actual HTTP request to your backend authentication service (e.g., using http package or Firebase Authentication).
  4. Based on the simulated response, it either shows a success message (via ScaffoldMessenger) or sets an _errorMessage.
  5. Finally, it sets _isLoading back to false in a finally block to ensure the loading indicator is hidden regardless of success or failure.
  6. The mounted check is important to prevent calling setState on a widget that has been removed from the widget tree.

Conclusion

Building a robust and user-friendly login form in Flutter involves combining various widgets, effectively managing state, and implementing proper input validation. By leveraging TextFormField, Form, and state management techniques, you can create a secure and professional authentication entry point for your applications. This foundation can be further extended with features like biometric authentication, social logins, password recovery flows, and more advanced error handling mechanisms to enhance the user experience and security.

Related Articles

Dec 19, 2025

Flutter & Firebase Auth: Seamless Social Media Login

Flutter & Firebase Auth: Seamless Social Media Login In today's digital landscape, user authentication is a critical component of almost every application. Pro

Dec 19, 2025

Building a Widget List with Sticky

Building a Widget List with Sticky Header in Flutter Creating dynamic and engaging user interfaces is crucial for modern applications. One common UI pattern th

Dec 19, 2025

Mastering Transform Scale & Rotate Animations in Flutter

Mastering Transform Scale & Rotate Animations in Flutter Flutter's powerful animation framework allows developers to create visually stunning and highly intera