Skip to main content

2 posts tagged with "Bloc"

View All Tags

5 Phương pháp quản lý state phổ biến trong Flutter

· 8 min read

Flutter State Management

Giới thiệu

Quản lý state là một trong những khía cạnh quan trọng nhất khi phát triển ứng dụng Flutter. Việc chọn đúng phương pháp quản lý state sẽ giúp code của bạn dễ bảo trì, mở rộng và hiệu quả hơn. Trong bài viết này, chúng ta sẽ khám phá 5 phương pháp quản lý state phổ biến nhất trong Flutter: Provider, Riverpod, Bloc, GetX, và MobX.

1. Provider

Tổng quan

Provider là giải pháp quản lý state được Google khuyến nghị và được xây dựng dựa trên InheritedWidget. Nó cung cấp một cách đơn giản và hiệu quả để quản lý state trong ứng dụng Flutter.

Đặc điểm

  • Đơn giản và dễ học: Syntax rõ ràng, dễ hiểu
  • Được Google khuyến nghị: Chính thức được Flutter team hỗ trợ
  • Hiệu suất tốt: Chỉ rebuild các widget cần thiết
  • Tích hợp tốt với Flutter: Sử dụng các widget có sẵn
  • Không cần code generation: Không cần build_runner

Khi nào sử dụng

  • Ứng dụng nhỏ đến trung bình
  • Team mới bắt đầu với Flutter
  • Cần giải pháp đơn giản, ít boilerplate
  • Muốn tuân theo best practices của Flutter

Ví dụ cơ bản

// Counter Provider
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;

void increment() {
_count++;
notifyListeners();
}
}

// Sử dụng
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text('Count: ${counter.count}');
},
)

Ưu điểm

  • Dễ học và sử dụng
  • Tài liệu phong phú
  • Cộng đồng lớn
  • Phù hợp với hầu hết các use case

Nhược điểm

  • Có thể phức tạp với ứng dụng lớn
  • Cần quản lý lifecycle thủ công
  • Không có compile-time safety mạnh

2. Riverpod

Tổng quan

Riverpod là phiên bản cải tiến của Provider, được tạo ra bởi cùng một tác giả. Nó giải quyết nhiều vấn đề của Provider và cung cấp compile-time safety tốt hơn.

Đặc điểm

  • Compile-time safety: Phát hiện lỗi tại compile time
  • Không cần BuildContext: Có thể truy cập providers từ bất kỳ đâu
  • Testable: Dễ dàng test và mock
  • Provider dependencies: Quản lý dependencies tự động
  • Code generation: Hỗ trợ code generation (optional)

Khi nào sử dụng

  • Ứng dụng lớn, phức tạp
  • Cần type safety cao
  • Muốn test dễ dàng
  • Cần quản lý dependencies tốt hơn

Ví dụ cơ bản

// Counter Provider
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);

void increment() => state++;
}

// Sử dụng
Consumer(
builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
},
)

Ưu điểm

  • Type-safe hơn Provider
  • Không cần BuildContext
  • Dễ test
  • Quản lý dependencies tốt
  • Hỗ trợ code generation

Nhược điểm

  • Learning curve cao hơn Provider
  • Syntax phức tạp hơn một chút
  • Cộng đồng nhỏ hơn Provider

3. Bloc

Tổng quan

Bloc (Business Logic Component) là một pattern quản lý state dựa trên các khái niệm từ ReactiveX. Nó tách biệt business logic khỏi UI và sử dụng streams để quản lý state.

Đặc điểm

  • Separation of concerns: Tách biệt logic và UI rõ ràng
  • Predictable: State changes có thể dự đoán được
  • Testable: Dễ test business logic
  • Reactive: Sử dụng streams và reactive programming
  • Time-travel debugging: Hỗ trợ debugging tốt

Khi nào sử dụng

  • Ứng dụng lớn, phức tạp
  • Cần quản lý business logic phức tạp
  • Team có kinh nghiệm với reactive programming
  • Cần test coverage cao

Ví dụ cơ bản

// Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}

// Bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementEvent>((event, emit) => emit(state + 1));
}
}

// Sử dụng
BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('Count: $count');
},
)

Ưu điểm

  • Architecture rõ ràng
  • Dễ test business logic
  • Hỗ trợ time-travel debugging
  • Phù hợp với ứng dụng lớn

Nhược điểm

  • Boilerplate code nhiều
  • Learning curve cao
  • Có thể overkill cho ứng dụng nhỏ

4. GetX

Tổng quan

GetX là một giải pháp all-in-one cho Flutter, không chỉ quản lý state mà còn cung cấp routing, dependency injection, và nhiều tính năng khác.

Đặc điểm

  • All-in-one: State management + Routing + DI
  • Hiệu suất cao: Tối ưu rebuild
  • Syntax đơn giản: Code ngắn gọn
  • Không cần BuildContext: Truy cập từ bất kỳ đâu
  • Tích hợp nhiều tính năng: Navigation, dialogs, snackbars

Khi nào sử dụng

  • Muốn giải pháp all-in-one
  • Cần routing đơn giản
  • Ưu tiên code ngắn gọn
  • Ứng dụng nhỏ đến trung bình

Ví dụ cơ bản

// Controller
class CounterController extends GetxController {
var count = 0.obs;

void increment() => count++;
}

// Sử dụng
final controller = Get.put(CounterController());

Obx(() => Text('Count: ${controller.count}'))

Ưu điểm

  • All-in-one solution
  • Code ngắn gọn
  • Hiệu suất tốt
  • Dễ sử dụng

Nhược điểm

  • Không được Flutter team chính thức khuyến nghị
  • Có thể khó maintain với ứng dụng lớn
  • Tight coupling giữa các tính năng

5. MobX

Tổng quan

MobX là một state management library được port từ JavaScript. Nó sử dụng reactive programming và code generation để quản lý state.

Đặc điểm

  • Reactive: Tự động update khi state thay đổi
  • Minimal boilerplate: Ít code lặp lại
  • Observable pattern: Sử dụng observables
  • Code generation: Sử dụng build_runner
  • Familiar: Nếu đã biết MobX từ JS/React

Khi nào sử dụng

  • Đã quen với MobX từ JavaScript/React
  • Cần reactive programming
  • Muốn ít boilerplate
  • Ứng dụng có state phức tạp

Ví dụ cơ bản

// Store
class CounterStore = _CounterStore with _$CounterStore;

abstract class _CounterStore with Store {

int count = 0;


void increment() => count++;
}

// Sử dụng
Observer(
builder: (_) => Text('Count: ${store.count}'),
)

Ưu điểm

  • Reactive và tự động
  • Ít boilerplate
  • Familiar cho dev từ JS background
  • Dễ sử dụng

Nhược điểm

  • Cần code generation
  • Cộng đồng nhỏ hơn
  • Learning curve nếu chưa quen với reactive

So sánh tổng quan

Tiêu chíProviderRiverpodBlocGetXMobX
Độ phổ biến⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Dễ học⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Type Safety⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Boilerplate⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Testability⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Hiệu suất⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Tài liệu⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Kết luận và khuyến nghị

Chọn Provider nếu:

  • Bạn mới bắt đầu với Flutter
  • Ứng dụng nhỏ đến trung bình
  • Muốn giải pháp đơn giản, được Google khuyến nghị

Chọn Riverpod nếu:

  • Bạn đã quen với Provider
  • Cần type safety cao
  • Ứng dụng lớn, phức tạp
  • Ưu tiên testability

Chọn Bloc nếu:

  • Ứng dụng lớn, phức tạp
  • Cần separation of concerns rõ ràng
  • Team có kinh nghiệm với reactive programming
  • Cần test coverage cao

Chọn GetX nếu:

  • Muốn giải pháp all-in-one
  • Ưu tiên code ngắn gọn
  • Ứng dụng nhỏ đến trung bình
  • Cần routing đơn giản

Chọn MobX nếu:

  • Đã quen với MobX từ JavaScript
  • Cần reactive programming
  • Muốn ít boilerplate
  • State phức tạp

Lời khuyên cuối cùng

  1. Bắt đầu đơn giản: Nếu mới bắt đầu, hãy thử Provider trước
  2. Xem xét dự án: Chọn phương pháp phù hợp với quy mô và độ phức tạp
  3. Team experience: Xem xét kinh nghiệm của team
  4. Long-term: Nghĩ về khả năng maintain và scale
  5. Thử nghiệm: Có thể thử nhiều phương pháp để tìm ra cái phù hợp nhất

Quản lý state là một phần quan trọng của Flutter development. Chọn đúng phương pháp sẽ giúp bạn xây dựng ứng dụng tốt hơn!

Hướng dẫn sử dụng Bloc trong ứng dụng đăng nhập

· 9 min read

Flutter Bloc Login App

Giới thiệu

Bloc (Business Logic Component) là một pattern quản lý state mạnh mẽ trong Flutter, đặc biệt phù hợp cho các ứng dụng có business logic phức tạp như authentication. Trong bài viết này, chúng ta sẽ xây dựng một hệ thống đăng nhập hoàn chỉnh sử dụng Bloc pattern.

Yêu cầu dự án

Ứng dụng đăng nhập sẽ có:

  • ✅ Màn hình đăng nhập với email và password
  • ✅ Validation form
  • ✅ Xử lý loading state
  • ✅ Hiển thị lỗi
  • ✅ Navigation sau khi đăng nhập thành công
  • ✅ Màn hình home sau khi đăng nhập

Bước 1: Setup dự án

Tạo Flutter project

flutter create bloc_login_app
cd bloc_login_app

Thêm dependencies

Thêm vào file pubspec.yaml:

dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.3
equatable: ^2.0.5
formz: ^0.6.1

Chạy lệnh:

flutter pub get

Bước 2: Tạo Models và Validators

Email Model

Tạo file lib/models/email.dart:

import 'package:formz/formz.dart';

enum EmailValidationError { invalid }

class Email extends FormzInput<String, EmailValidationError> {
const Email.pure() : super.pure('');
const Email.dirty([super.value = '']) : super.dirty();

static final _emailRegex = RegExp(
r'^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$',
);


EmailValidationError? validator(String value) {
return _emailRegex.hasMatch(value) ? null : EmailValidationError.invalid;
}
}

Password Model

Tạo file lib/models/password.dart:

import 'package:formz/formz.dart';

enum PasswordValidationError { invalid }

class Password extends FormzInput<String, PasswordValidationError> {
const Password.pure() : super.pure('');
const Password.dirty([super.value = '']) : super.dirty();


PasswordValidationError? validator(String value) {
return value.length >= 6 ? null : PasswordValidationError.invalid;
}
}

Bước 3: Tạo Login Events

Tạo file lib/bloc/login/login_event.dart:

import 'package:equatable/equatable.dart';
import '../models/email.dart';
import '../models/password.dart';

abstract class LoginEvent extends Equatable {
const LoginEvent();


List<Object> get props => [];
}

class LoginEmailChanged extends LoginEvent {
final String email;

const LoginEmailChanged(this.email);


List<Object> get props => [email];
}

class LoginPasswordChanged extends LoginEvent {
final String password;

const LoginPasswordChanged(this.password);


List<Object> get props => [password];
}

class LoginSubmitted extends LoginEvent {
const LoginSubmitted();
}

class LoginPasswordVisibilityToggled extends LoginEvent {
const LoginPasswordVisibilityToggled();
}

Bước 4: Tạo Login State

Tạo file lib/bloc/login/login_state.dart:

import 'package:equatable/equatable.dart';
import 'package:formz/formz.dart';
import '../models/email.dart';
import '../models/password.dart';

class LoginState extends Equatable {
const LoginState({
this.email = const Email.pure(),
this.password = const Password.pure(),
this.status = FormzSubmissionStatus.initial,
this.isPasswordVisible = false,
this.errorMessage,
});

final Email email;
final Password password;
final FormzSubmissionStatus status;
final bool isPasswordVisible;
final String? errorMessage;

bool get isValid => Formz.validate([email, password]);

LoginState copyWith({
Email? email,
Password? password,
FormzSubmissionStatus? status,
bool? isPasswordVisible,
String? errorMessage,
}) {
return LoginState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
isPasswordVisible: isPasswordVisible ?? this.isPasswordVisible,
errorMessage: errorMessage ?? this.errorMessage,
);
}


List<Object?> get props => [
email,
password,
status,
isPasswordVisible,
errorMessage,
];
}

Bước 5: Tạo Login Bloc

Tạo file lib/bloc/login/login_bloc.dart:

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:formz/formz.dart';
import 'login_event.dart';
import 'login_state.dart';
import '../models/email.dart';
import '../models/password.dart';

class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc() : super(const LoginState()) {
on<LoginEmailChanged>(_onEmailChanged);
on<LoginPasswordChanged>(_onPasswordChanged);
on<LoginSubmitted>(_onSubmitted);
on<LoginPasswordVisibilityToggled>(_onPasswordVisibilityToggled);
}

void _onEmailChanged(
LoginEmailChanged event,
Emitter<LoginState> emit,
) {
final email = Email.dirty(event.email);
emit(
state.copyWith(
email: email,
status: Formz.validate([email, state.password])
? FormzSubmissionStatus.initial
: FormzSubmissionStatus.initial,
),
);
}

void _onPasswordChanged(
LoginPasswordChanged event,
Emitter<LoginState> emit,
) {
final password = Password.dirty(event.password);
emit(
state.copyWith(
password: password,
status: Formz.validate([state.email, password])
? FormzSubmissionStatus.initial
: FormzSubmissionStatus.initial,
),
);
}

void _onPasswordVisibilityToggled(
LoginPasswordVisibilityToggled event,
Emitter<LoginState> emit,
) {
emit(state.copyWith(isPasswordVisible: !state.isPasswordVisible));
}

Future<void> _onSubmitted(
LoginSubmitted event,
Emitter<LoginState> emit,
) async {
if (state.isValid) {
emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
try {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));

// Mock authentication logic
if (state.email.value == 'user@example.com' &&
state.password.value == 'password123') {
emit(state.copyWith(status: FormzSubmissionStatus.success));
} else {
emit(
state.copyWith(
status: FormzSubmissionStatus.failure,
errorMessage: 'Email hoặc mật khẩu không đúng',
),
);
}
} catch (error) {
emit(
state.copyWith(
status: FormzSubmissionStatus.failure,
errorMessage: 'Đã xảy ra lỗi. Vui lòng thử lại.',
),
);
}
}
}
}

Bước 6: Tạo Auth Repository (Optional)

Tạo file lib/repositories/auth_repository.dart:

class AuthRepository {
Future<bool> login(String email, String password) async {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));

// Mock authentication
if (email == 'user@example.com' && password == 'password123') {
return true;
}
return false;
}

Future<void> logout() async {
// Clear user session
await Future.delayed(const Duration(milliseconds: 500));
}
}

Bước 7: Tạo Login Screen

Tạo file lib/screens/login_screen.dart:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/login/login_bloc.dart';
import '../bloc/login/login_event.dart';
import '../bloc/login/login_state.dart';
import 'home_screen.dart';

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


Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => LoginBloc(),
child: const LoginForm(),
),
);
}
}

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


Widget build(BuildContext context) {
return BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state.status.isSuccess) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
} else if (state.status.isFailure) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(state.errorMessage ?? 'Đăng nhập thất bại'),
backgroundColor: Colors.red,
),
);
}
},
child: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.lock_outline,
size: 80,
color: Colors.blue,
),
const SizedBox(height: 32),
const Text(
'Đăng Nhập',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 48),
const _EmailInput(),
const SizedBox(height: 16),
const _PasswordInput(),
const SizedBox(height: 24),
const _LoginButton(),
const SizedBox(height: 16),
const _DemoCredentials(),
],
),
),
),
),
);
}
}

class _EmailInput extends StatelessWidget {
const _EmailInput();


Widget build(BuildContext context) {
return BlocBuilder<LoginBloc, LoginState>(
buildWhen: (previous, current) => previous.email != current.email,
builder: (context, state) {
return TextField(
key: const Key('loginForm_emailInput_textField'),
onChanged: (email) =>
context.read<LoginBloc>().add(LoginEmailChanged(email)),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: const Icon(Icons.email),
errorText: state.email.displayError != null
? 'Email không hợp lệ'
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
);
},
);
}
}

class _PasswordInput extends StatelessWidget {
const _PasswordInput();


Widget build(BuildContext context) {
return BlocBuilder<LoginBloc, LoginState>(
buildWhen: (previous, current) =>
previous.password != current.password ||
previous.isPasswordVisible != current.isPasswordVisible,
builder: (context, state) {
return TextField(
key: const Key('loginForm_passwordInput_textField'),
onChanged: (password) =>
context.read<LoginBloc>().add(LoginPasswordChanged(password)),
obscureText: !state.isPasswordVisible,
decoration: InputDecoration(
labelText: 'Mật khẩu',
prefixIcon: const Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
state.isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () => context
.read<LoginBloc>()
.add(const LoginPasswordVisibilityToggled()),
),
errorText: state.password.displayError != null
? 'Mật khẩu phải có ít nhất 6 ký tự'
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
);
},
);
}
}

class _LoginButton extends StatelessWidget {
const _LoginButton();


Widget build(BuildContext context) {
return BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
key: const Key('loginForm_continue_raisedButton'),
onPressed: state.isValid && !state.status.isInProgress
? () => context.read<LoginBloc>().add(const LoginSubmitted())
: null,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: state.status.isInProgress
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text(
'Đăng Nhập',
style: TextStyle(fontSize: 16),
),
),
);
},
);
}
}

class _DemoCredentials extends StatelessWidget {
const _DemoCredentials();


Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
const Text(
'Thông tin đăng nhập demo:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text('Email: user@example.com'),
const Text('Password: password123'),
],
),
);
}
}

Bước 8: Tạo Home Screen

Tạo file lib/screens/home_screen.dart:

import 'package:flutter/material.dart';

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


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Trang Chủ'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () {
Navigator.of(context).pop();
},
tooltip: 'Đăng xuất',
),
],
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check_circle,
size: 80,
color: Colors.green,
),
SizedBox(height: 24),
Text(
'Đăng nhập thành công!',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Text(
'Chào mừng bạn đến với ứng dụng',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
),
);
}
}

Bước 9: Setup main.dart

Cập nhật file lib/main.dart:

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);


Widget build(BuildContext context) {
return MaterialApp(
title: 'Bloc Login App',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const LoginScreen(),
debugShowCheckedModeBanner: false,
);
}
}

Giải thích các khái niệm Bloc

1. Events

Events đại diện cho các hành động từ UI (user input, button clicks, etc.). Mỗi event là một class extends Equatable.

2. States

States đại diện cho trạng thái hiện tại của ứng dụng. State class cũng extends Equatable để so sánh.

3. Bloc

Bloc nhận Events và emit States. Nó chứa business logic và xử lý các side effects.

4. BlocBuilder

BlocBuilder rebuild UI khi state thay đổi. Sử dụng buildWhen để kiểm soát khi nào rebuild.

5. BlocListener

BlocListener lắng nghe state changes và thực hiện side effects (navigation, show snackbar, etc.) mà không rebuild UI.

Best Practices

1. Separation of Concerns

  • Events: User actions
  • States: UI state
  • Bloc: Business logic

2. Immutable States

Luôn tạo state mới thay vì modify state hiện tại:

// ✅ Good
emit(state.copyWith(email: newEmail));

// ❌ Bad
state.email = newEmail;
emit(state);

3. Use Equatable

Sử dụng Equatable để so sánh states và events hiệu quả.

4. Error Handling

Luôn xử lý errors và emit appropriate states:

try {
// API call
} catch (error) {
emit(state.copyWith(
status: FormzSubmissionStatus.failure,
errorMessage: error.toString(),
));
}

5. Testing

Bloc dễ test vì logic tách biệt khỏi UI:

test('emits [LoginState] when email changes', () {
final bloc = LoginBloc();
bloc.add(const LoginEmailChanged('test@example.com'));
expect(bloc.state.email.value, 'test@example.com');
});

Mở rộng ứng dụng

Thêm Remember Me

class LoginRememberMeToggled extends LoginEvent {
const LoginRememberMeToggled();
}

// In state
final bool rememberMe;

// In bloc
void _onRememberMeToggled(...) {
emit(state.copyWith(rememberMe: !state.rememberMe));
}

Thêm Forgot Password

Tạo ForgotPasswordBloc tương tự LoginBloc.

Tích hợp với API thật

Thay thế mock logic bằng API calls:

Future<void> _onSubmitted(...) async {
if (state.isValid) {
emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
try {
final success = await authRepository.login(
state.email.value,
state.password.value,
);
if (success) {
emit(state.copyWith(status: FormzSubmissionStatus.success));
} else {
emit(state.copyWith(
status: FormzSubmissionStatus.failure,
errorMessage: 'Đăng nhập thất bại',
));
}
} catch (error) {
emit(state.copyWith(
status: FormzSubmissionStatus.failure,
errorMessage: error.toString(),
));
}
}
}

Kết luận

Trong bài viết này, chúng ta đã xây dựng một hệ thống đăng nhập hoàn chỉnh sử dụng Bloc pattern. Các điểm chính:

  1. ✅ Tạo Events và States
  2. ✅ Implement LoginBloc với business logic
  3. ✅ Sử dụng BlocBuilder và BlocListener
  4. ✅ Form validation với Formz
  5. ✅ Xử lý loading và error states

Bloc pattern cung cấp một architecture rõ ràng và dễ test, đặc biệt phù hợp cho các ứng dụng có business logic phức tạp. Hãy tiếp tục khám phá và mở rộng ứng dụng này!

Happy coding với Flutter và Bloc!