Hello Flutter developers! Today, we'll create a modern, beautiful login screen with animations, form validation, and proper error handling. This tutorial will show you how to build a professional-looking login interface from scratch.
What We'll Build
- A clean, modern login interface
- Form validation
- Error handling
- Loading animations
- Custom button designs
- Input field decorations
Prerequisites
- Basic knowledge of Flutter
- Flutter development environment set up
- Text editor or IDE
Step 1: Setting Up the Project Structure
First, let's create a clean project structure:
lib/
├── screens/
│ └── login_screen.dart
├── widgets/
│ └── custom_button.dart
└── main.dart
Step 2: Creating the Login Screen
Here's our complete login_screen.dart
:
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
bool _isPasswordVisible = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
_buildHeader(),
const SizedBox(height: 40),
_buildLoginForm(),
const SizedBox(height: 20),
_buildLoginButton(),
const SizedBox(height: 16),
_buildSignUpLink(),
],
),
),
),
);
}
Widget _buildHeader() {
return Column(
children: const [
Text(
'Welcome Back!',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Sign in to continue',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
);
}
Widget _buildLoginForm() {
return Form(
key: _formKey,
child: Column(
children: [
// Email Field
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.grey, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.blue, width: 2),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
// Password Field
TextFormField(
controller: _passwordController,
obscureText: !_isPasswordVisible,
decoration: InputDecoration(
labelText: 'Password',
hintText: 'Enter your password',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.grey, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.blue, width: 2),
),
),
validator: (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;
},
),
const SizedBox(height: 12),
// Forgot Password Link
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
// Handle forgot password
},
child: const Text('Forgot Password?'),
),
),
],
),
);
}
Widget _buildLoginButton() {
return ElevatedButton(
onPressed: _isLoading ? null : _handleLogin,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text(
'Login',
style: TextStyle(fontSize: 16),
),
);
}
Widget _buildSignUpLink() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Don't have an account?"),
TextButton(
onPressed: () {
// Handle sign up navigation
},
child: const Text('Sign Up'),
),
],
);
}
Future<void> _handleLogin() async {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});
try {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
// Handle successful login
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login successful!'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}
}
Key Features Explanation
1. Form Validation
We've implemented comprehensive form validation:
- Email validation using RegExp
- Password length check
- Empty field validation
2. Loading State
The login button shows a loading spinner when:
- User taps login
- Form validation passes
- API call is in progress
3. Error Handling
We handle errors gracefully:
- Show error messages in SnackBar
- Clear loading state
- Preserve form data
4. UI/UX Features
- Password visibility toggle
- Proper keyboard types
- Beautiful input decorations
- Responsive layout
- Loading animations
How to Use This Code
- Create a new Flutter project
- Replace your main.dart content:
import 'package:flutter/material.dart';
import 'screens/login_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Login Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.grey[50],
),
),
home: const LoginScreen(),
);
}
}
Customization Options
Colors and Themes
To change the color scheme:
theme: ThemeData(
primarySwatch: Colors.purple, // Change primary color
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.grey[50],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
Button Styles
To customize the login button:
ElevatedButton.styleFrom(
primary: Colors.purple, // Background color
onPrimary: Colors.white, // Text color
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
)
Best Practices Used
-
State Management
- Proper use of StatefulWidget
- Controller disposal
- Loading state handling
-
Form Handling
- GlobalKey for form
- Comprehensive validation
- Proper keyboard types
-
Error Handling
- Try-catch blocks
- User feedback
- Graceful error recovery
-
UI/UX
- Consistent spacing
- Clear feedback
- Loading indicators
- Proper input validation
Common Issues and Solutions
-
Form not validating?
- Check if _formKey is properly connected
- Ensure validators return null for valid input
-
Loading spinner not showing?
- Verify setState is called
- Check if _isLoading is toggled
-
Keyboard covering input fields?
- Wrap with SingleChildScrollView
- Use resizeToAvoidBottomInset
Next Steps
To enhance this login screen, consider adding:
- Social login buttons
- Biometric authentication
- Remember me functionality
- Custom animations
- Dark mode support
Conclusion
You now have a professional-looking login screen with proper validation, error handling, and user feedback. This code provides a solid foundation that you can customize and build upon for your specific needs.
Found this helpful? Follow me for more Flutter tutorials! Feel free to ask questions in the comments.
Top comments (0)