November 7, 2025
Flutter

How to create a login page using flutter step by step guide

let’s build a clean, practical Flutter login page step-by-step. I’ll give you a working example (with full main.dart, login_page.dart, and home_page.dart) plus explanations and tips to extend it for a real backend.


1) Create a new Flutter project

Run in terminal:

flutter create flutter_login_demo
cd flutter_login_demo

No extra packages are required for the basic demo. (If you later want real auth, you’ll add http, flutter_secure_storage, or a state manager like bloc/provider.)


2) Project structure (simple)

lib/
  main.dart
  pages/
    login_page.dart
    home_page.dart

3) Full code — copy into files

lib/main.dart

import 'package:flutter/material.dart';
import 'pages/login_page.dart';
import 'pages/home_page.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',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.indigo,
      ),
      initialRoute: '/',
      routes: {
        '/': (context) => const LoginPage(),
        '/home': (context) => const HomePage(),
      },
    );
  }
}

lib/pages/login_page.dart

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

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

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

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _loading = false;
  String? _errorMessage;

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

  // Mock authentication function to simulate a network call
  Future<bool> _fakeAuthenticate(String email, String password) async {
    await Future.delayed(const Duration(seconds: 2)); // simulate network
    // Example: accept only this credential pair
    return email.trim().toLowerCase() == 'test@example.com' && password == 'password';
  }

  void _submit() async {
    setState(() {
      _errorMessage = null;
    });

    if (!_formKey.currentState!.validate()) return;

    final email = _emailController.text;
    final password = _passwordController.text;

    setState(() => _loading = true);

    try {
      final ok = await _fakeAuthenticate(email, password);
      if (ok) {
        // Navigate to home and remove login from stack
        if (!mounted) return;
        Navigator.of(context).pushReplacementNamed('/home');
      } else {
        setState(() {
          _errorMessage = 'Invalid email or password.';
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'An error occurred. Try again.';
      });
    } finally {
      if (mounted) {
        setState(() => _loading = false);
      }
    }
  }

  String? _validateEmail(String? value) {
    if (value == null || value.trim().isEmpty) return 'Please enter your email';
    final email = value.trim();
    final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');
    if (!emailRegex.hasMatch(email)) return 'Enter a valid email';
    return null;
  }

  String? _validatePassword(String? value) {
    if (value == null || value.isEmpty) return 'Please enter your password';
    if (value.length < 6) return 'Password must be at least 6 characters';
    return null;
  }

  @override
  Widget build(BuildContext context) {
    // Responsive width constraint
    final width = MediaQuery.of(context).size.width;
    final formWidth = width > 600 ? 480.0 : width * 0.9;

    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.symmetric(vertical: 24),
          child: ConstrainedBox(
            constraints: BoxConstraints(maxWidth: formWidth),
            child: Card(
              elevation: 6,
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
              margin: const EdgeInsets.all(16),
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Form(
                  key: _formKey,
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const FlutterLogo(size: 72),
                      const SizedBox(height: 12),
                      const Text('Welcome back', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
                      const SizedBox(height: 20),

                      if (_errorMessage != null) ...[
                        Container(
                          width: double.infinity,
                          padding: const EdgeInsets.all(12),
                          margin: const EdgeInsets.only(bottom: 12),
                          decoration: BoxDecoration(
                            color: Theme.of(context).colorScheme.error.withOpacity(0.1),
                            borderRadius: BorderRadius.circular(8),
                          ),
                          child: Text(
                            _errorMessage!,
                            style: TextStyle(color: Theme.of(context).colorScheme.error),
                          ),
                        ),
                      ],

                      TextFormField(
                        controller: _emailController,
                        keyboardType: TextInputType.emailAddress,
                        decoration: const InputDecoration(
                          labelText: 'Email',
                          prefixIcon: Icon(Icons.email),
                          border: OutlineInputBorder(),
                        ),
                        validator: _validateEmail,
                        autofillHints: const [AutofillHints.username, AutofillHints.email],
                      ),
                      const SizedBox(height: 12),
                      TextFormField(
                        controller: _passwordController,
                        obscureText: true,
                        decoration: const InputDecoration(
                          labelText: 'Password',
                          prefixIcon: Icon(Icons.lock),
                          border: OutlineInputBorder(),
                        ),
                        validator: _validatePassword,
                        autofillHints: const [AutofillHints.password],
                      ),
                      const SizedBox(height: 8),

                      Align(
                        alignment: Alignment.centerRight,
                        child: TextButton(
                          onPressed: () {
                            // stub: implement forgot password flow
                            ScaffoldMessenger.of(context).showSnackBar(
                              const SnackBar(content: Text('Forgot password tapped (not implemented)')),
                            );
                          },
                          child: const Text('Forgot password?'),
                        ),
                      ),
                      const SizedBox(height: 8),

                      SizedBox(
                        width: double.infinity,
                        height: 48,
                        child: ElevatedButton(
                          onPressed: _loading ? null : _submit,
                          child: _loading
                              ? const SizedBox(
                                  width: 24,
                                  height: 24,
                                  child: CircularProgressIndicator(strokeWidth: 2),
                                )
                              : const Text('Sign in'),
                        ),
                      ),
                      const SizedBox(height: 12),

                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          const Text("Don't have an account?"),
                          TextButton(
                            onPressed: () {
                              // stub: implement registration flow
                              ScaffoldMessenger.of(context).showSnackBar(
                                const SnackBar(content: Text('Register tapped (not implemented)')),
                              );
                            },
                            child: const Text('Register'),
                          )
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Notes on the login code

  • Uses Form + validators (email + password length).
  • Uses controllers and dispose() correctly.
  • Displays an inline error message area and a loading spinner in the button.
  • _fakeAuthenticate simulates network latency and accepts only test@example.com / password. Change for real auth.

lib/pages/home_page.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        actions: [
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () {
              // navigate back to login (clear stack)
              Navigator.of(context).pushReplacementNamed('/');
            },
          )
        ],
      ),
      body: const Center(
        child: Text('You are signed in!', style: TextStyle(fontSize: 18)),
      ),
    );
  }
}

4) Run the app

flutter run

Open an emulator or physical device. Use credentials:

  • Email: test@example.com
  • Password: password

5) How to connect to a real backend (overview)

  1. Add http package: flutter pub add http
  2. In _submit() replace _fakeAuthenticate with an HTTP POST to your API (send email/password).
  3. On success, store token securely: use flutter_secure_storage or platform secure storage.
  4. Use token in subsequent API calls (add Authorization header).
  5. Implement error handling for network/timeouts and show friendly messages.

6) Improvements & best practices

  • Move auth logic into a separate service class (e.g., AuthService) → easier testing.
  • Use state management (Provider / Riverpod / Bloc) for larger apps.
  • Add form focus traversal and improved accessibility (labels, semantics).
  • Use HTTPS, validate tokens on app start, refresh tokens if needed.
  • Add unit/widget tests: test validation and navigation flows.

7) Quick checklist before production

  • Use secure storage for tokens.
  • Protect API keys and secrets.
  • Validate server responses and show specific error messages.
  • Implement signup / forgot-password flows and email verification.
  • Add rate-limiting/backoff for retries.

Related posts

How to Make Image to Pdf Converter App Using Flutter

Dheeraj Pal

How to integrate google map in flutter application step by step guide

Dheeraj Pal

Dio interceptors in flutter example

Dheeraj Pal

Leave a Comment