🧱 BLoC Pattern
1. Giới thiệu
Phần tiêu đề “1. Giới thiệu”BLoC (Business Logic Component) tách biệt business logic khỏi UI:
UI ──Events──▶ BLoC ──States──▶ UIdependencies: flutter_bloc: ^8.1.02. Các khái niệm cốt lõi
Phần tiêu đề “2. Các khái niệm cốt lõi”| Khái niệm | Mô tả |
|---|---|
| Event | Input từ UI (user actions) |
| State | Output cho UI (data to display) |
| BLoC | Xử lý Events và emit States |
3. Ví dụ cơ bản - Counter
Phần tiêu đề “3. Ví dụ cơ bản - Counter”Định nghĩa Events
Phần tiêu đề “Định nghĩa Events”abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}class DecrementEvent extends CounterEvent {}Định nghĩa States
Phần tiêu đề “Định nghĩa States”class CounterState { final int count; const CounterState(this.count);}Định nghĩa BLoC
Phần tiêu đề “Định nghĩa BLoC”class CounterBloc extends Bloc<CounterEvent, CounterState> { CounterBloc() : super(const CounterState(0)) { on<IncrementEvent>((event, emit) { emit(CounterState(state.count + 1)); });
on<DecrementEvent>((event, emit) { emit(CounterState(state.count - 1)); }); }}4. Provide BLoC
Phần tiêu đề “4. Provide BLoC”void main() { runApp( BlocProvider( create: (context) => CounterBloc(), child: MyApp(), ), );}
// Hoặc multipleMultiBlocProvider( providers: [ BlocProvider(create: (_) => CounterBloc()), BlocProvider(create: (_) => AuthBloc()), ], child: MyApp(),)5. Consume BLoC
Phần tiêu đề “5. Consume BLoC”BlocBuilder
Phần tiêu đề “BlocBuilder”BlocBuilder<CounterBloc, CounterState>( builder: (context, state) { return Text('Count: ${state.count}'); },)context.watch / context.read
Phần tiêu đề “context.watch / context.read”class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // watch: rebuild khi state thay đổi Text('${context.watch<CounterBloc>().state.count}'),
ElevatedButton( // read: gửi event onPressed: () => context.read<CounterBloc>().add(IncrementEvent()), child: Text('+'), ), ], ); }}6. BlocListener - Side Effects
Phần tiêu đề “6. BlocListener - Side Effects”Không rebuild UI, chỉ thực hiện side effects:
BlocListener<AuthBloc, AuthState>( listener: (context, state) { if (state is AuthSuccess) { Navigator.pushReplacementNamed(context, '/home'); } else if (state is AuthFailure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.error)), ); } }, child: LoginForm(),)7. BlocConsumer - Builder + Listener
Phần tiêu đề “7. BlocConsumer - Builder + Listener”BlocConsumer<AuthBloc, AuthState>( listener: (context, state) { // Side effects if (state is AuthFailure) { ScaffoldMessenger.of(context).showSnackBar(...); } }, builder: (context, state) { // UI if (state is AuthLoading) return CircularProgressIndicator(); return LoginForm(); },)8. Ví dụ thực tế - Todo BLoC
Phần tiêu đề “8. Ví dụ thực tế - Todo BLoC”Events
Phần tiêu đề “Events”abstract class TodoEvent {}
class LoadTodos extends TodoEvent {}
class AddTodo extends TodoEvent { final String title; AddTodo(this.title);}
class ToggleTodo extends TodoEvent { final String id; ToggleTodo(this.id);}
class DeleteTodo extends TodoEvent { final String id; DeleteTodo(this.id);}States
Phần tiêu đề “States”abstract class TodoState {}
class TodoInitial extends TodoState {}class TodoLoading extends TodoState {}class TodoLoaded extends TodoState { final List<Todo> todos; TodoLoaded(this.todos);}class TodoError extends TodoState { final String message; TodoError(this.message);}class TodoBloc extends Bloc<TodoEvent, TodoState> { final TodoRepository repository;
TodoBloc(this.repository) : super(TodoInitial()) { on<LoadTodos>(_onLoadTodos); on<AddTodo>(_onAddTodo); on<ToggleTodo>(_onToggleTodo); on<DeleteTodo>(_onDeleteTodo); }
Future<void> _onLoadTodos(LoadTodos event, Emitter<TodoState> emit) async { emit(TodoLoading()); try { final todos = await repository.getTodos(); emit(TodoLoaded(todos)); } catch (e) { emit(TodoError(e.toString())); } }
void _onAddTodo(AddTodo event, Emitter<TodoState> emit) { if (state is TodoLoaded) { final currentTodos = (state as TodoLoaded).todos; final newTodo = Todo(id: DateTime.now().toString(), title: event.title); emit(TodoLoaded([...currentTodos, newTodo])); } }
// ... other handlers}class TodoPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: BlocBuilder<TodoBloc, TodoState>( builder: (context, state) { if (state is TodoLoading) { return Center(child: CircularProgressIndicator()); } if (state is TodoError) { return Center(child: Text(state.message)); } if (state is TodoLoaded) { return ListView.builder( itemCount: state.todos.length, itemBuilder: (context, index) { final todo = state.todos[index]; return ListTile( title: Text(todo.title), onTap: () => context.read<TodoBloc>().add(ToggleTodo(todo.id)), ); }, ); } return Container(); }, ), ); }}9. Cubit - Simplified BLoC
Phần tiêu đề “9. Cubit - Simplified BLoC”Không cần Events, gọi methods trực tiếp:
class CounterCubit extends Cubit<int> { CounterCubit() : super(0);
void increment() => emit(state + 1); void decrement() => emit(state - 1);}
// Sử dụngcontext.read<CounterCubit>().increment();10. Khi nào dùng BLoC?
Phần tiêu đề “10. Khi nào dùng BLoC?”| Use case | Recommendation |
|---|---|
| Simple state | setState hoặc Provider |
| Medium complexity | Cubit hoặc Riverpod |
| Complex, event-driven | BLoC |
| Large team, enterprise | BLoC |
📝 Tóm tắt
Phần tiêu đề “📝 Tóm tắt”| Component | Vai trò |
|---|---|
Event |
User actions |
State |
UI data |
Bloc |
Event → State transformation |
Cubit |
Simplified Bloc |
BlocBuilder |
Rebuild UI on state change |
BlocListener |
Side effects |
BlocConsumer |
Builder + Listener |