๐ Flutter BLoC Login / Authentication Example
Authentication is one of the most crucial components in any application. With Flutter, developers can build robust and scalable authentication flows using the BLoC (Business Logic Component) pattern. It separates UI from logic, ensuring maintainability and clarity in code. In this guide, we’ll build a complete Login and Registration System using Flutter and BLoC, backed by a fake JSON REST API powered by json-server
.
Instead of Firebase or other cloud services, we simulate API calls locally using json-server. This approach is ideal for testing authentication logic without deploying a real backend. The app includes user registration and login with real-time form validation, loading states, success/failure feedback, and navigation post-authentication โ all handled gracefully using BLoC.
Weโll cover:
- Folder structure
- AuthRepository with HTTP requests
- Events and States for Login and Registration
- AuthBloc logic
- Fully functional Login and Register UI screens
This system is perfect for beginners and intermediate developers looking to master Flutter BLoC patterns while building real-world features.
๐ Folder Structure
lib/
โโโ auth/
โ โโโ auth_bloc.dart
โ โโโ auth_event.dart
โ โโโ auth_state.dart
โ โโโ auth_repository.dart
โโโ login/
โ โโโ login_screen.dart
โโโ register/
โ โโโ register_screen.dart
โโโ main.dart
๐ auth_repository.dart
import 'dart:convert'; import 'package:http/http.dart' as http; class AuthRepository { final String baseUrl = 'http://localhost:3000/users'; // Make sure this is accessible on your device/emulator Future<bool> login({required String email, required String password}) async { final url = Uri.parse('$baseUrl?email=$email&password=$password'); print('Login request: $url'); final response = await http.get(url); print('Login response: ${response.statusCode} - ${response.body}'); return jsonDecode(response.body).isNotEmpty; } Future<bool> register( {required String email, required String password}) async { final url = Uri.parse(baseUrl); final body = jsonEncode({'email': email, 'password': password}); print('Register request to: $url'); print('Register payload: $body'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: body, ); print('Register response status: ${response.statusCode}'); print('Register response body: ${response.body}'); return response.statusCode == 201; } }
๐ฆ auth_event.dart
abstract class AuthEvent {} class LoginRequested extends AuthEvent { final String email; final String password; LoginRequested({required this.email, required this.password}); } class RegisterRequested extends AuthEvent { final String email; final String password; RegisterRequested({required this.email, required this.password}); }
๐ฆ auth_state.dart
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthSuccess extends AuthState {}
class AuthFailure extends AuthState {
final String message;
AuthFailure(this.message);
}
๐ฆ auth_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth_event.dart';
import 'auth_state.dart';
import 'auth_repository.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository authRepository;
AuthBloc(this.authRepository) : super(AuthInitial()) {
on(_onLoginRequested);
on(_onRegisterRequested);
}
Future _onLoginRequested(LoginRequested event, Emitter emit) async {
emit(AuthLoading());
try {
final success = await authRepository.login(email: event.email, password: event.password);
emit(success ? AuthSuccess() : AuthFailure("Invalid credentials"));
} catch (_) {
emit(AuthFailure("An error occurred"));
}
}
Future _onRegisterRequested(RegisterRequested event, Emitter emit) async {
emit(AuthLoading());
try {
final success = await authRepository.register(email: event.email, password: event.password);
emit(success ? AuthSuccess() : AuthFailure("Registration failed"));
} catch (_) {
emit(AuthFailure("An error occurred"));
}
}
}
๐งช login_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../auth/auth_bloc.dart';
import '../auth/auth_event.dart';
import '../auth/auth_state.dart';
import '../register/register_screen.dart';
class LoginScreen extends StatelessWidget {
final _formKey = GlobalKey();
String email = '', password = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.message)));
} else if (state is AuthSuccess) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Login Successful')));
}
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (val) => email = val,
validator: (val) => val!.contains('@') ? null : 'Enter a valid email',
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: (val) => password = val,
validator: (val) => val!.length >= 6 ? null : 'Minimum 6 characters',
),
const SizedBox(height: 20),
BlocBuilder<AuthBloc, AuthState>(builder: (context, state) {
if (state is AuthLoading) return CircularProgressIndicator();
return ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
context.read().add(LoginRequested(email: email, password: password));
}
},
child: Text('Login'),
);
}),
TextButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => RegisterScreen())),
child: Text('No account? Register here'),
)
]),
),
),
),
);
}
}
๐งช register_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../auth/auth_bloc.dart';
import '../auth/auth_event.dart';
import '../auth/auth_state.dart';
class RegisterScreen extends StatelessWidget {
final _formKey = GlobalKey();
String email = '', password = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Register')),
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.message)));
} else if (state is AuthSuccess) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Registration Successful')));
Navigator.pop(context);
}
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (val) => email = val,
validator: (val) => val!.contains('@') ? null : 'Enter a valid email',
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: (val) => password = val,
validator: (val) => val!.length >= 6 ? null : 'Minimum 6 characters',
),
const SizedBox(height: 20),
BlocBuilder<AuthBloc, AuthState>(builder: (context, state) {
if (state is AuthLoading) return CircularProgressIndicator();
return ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
context.read().add(RegisterRequested(email: email, password: password));
}
},
child: Text('Register'),
);
}),
]),
),
),
),
);
}
}
๐ main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth/auth_repository.dart';
import 'auth/auth_bloc.dart';
import 'login/login_screen.dart';
import 'register/register_screen.dart'; // import if needed
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final AuthRepository authRepository = AuthRepository();
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AuthBloc(authRepository),
child: MaterialApp(
title: 'Flutter Auth Demo',
debugShowCheckedModeBanner: false,
home: LoginScreen(),
routes: {
'/register': (context) => RegisterScreen(),
// add other routes here
},
),
);
}
}
๐ json-server db.json
{
"users": []
}
Run this command: json-server --watch db.json --port 3000
โ Summary
Layer | File | Responsibility |
---|---|---|
Repository | auth_repository.dart |
Business logic (fake login) |
Bloc | auth_bloc.dart |
Handles logic for events & state updates |
Events/States | auth_event.dart , auth_state.dart |
Structure for communication |
UI | login_screen.dart , register_screen.dart |
Form + BLoC listener + validator |