Skip to Content

BLoC Pattern trong Flutter

1. Giới thiệu

BLoC (Business Logic Component) tách biệt business logic khỏi UI:

UI ──Events──▶ BLoC ──States──▶ UI
# pubspec.yaml dependencies: flutter_bloc: ^8.1.0

2. Các khái niệm cốt lõi

Khái niệmMô tả
EventInput từ UI (user actions)
StateOutput cho UI (data to display)
BLoCXử lý Events và emit States

3. Ví dụ cơ bản - Counter

Định nghĩa Events

abstract class CounterEvent {} class IncrementEvent extends CounterEvent {} class DecrementEvent extends CounterEvent {}

Định nghĩa States

class CounterState { final int count; const CounterState(this.count); }

Đị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

void main() { runApp( BlocProvider( create: (context) => CounterBloc(), child: MyApp(), ), ); } // Hoặc multiple MultiBlocProvider( providers: [ BlocProvider(create: (_) => CounterBloc()), BlocProvider(create: (_) => AuthBloc()), ], child: MyApp(), )

5. Consume BLoC

BlocBuilder

BlocBuilder<CounterBloc, CounterState>( builder: (context, state) { return Text('Count: ${state.count}'); }, )

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

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

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

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

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

BLoC

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 }

UI

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

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ụng context.read<CounterCubit>().increment();

10. Khi nào dùng BLoC?

Use caseRecommendation
Simple statesetState hoặc Provider
Medium complexityCubit hoặc Riverpod
Complex, event-drivenBLoC
Large team, enterpriseBLoC

📝 Tóm tắt

ComponentVai trò
EventUser actions
StateUI data
BlocEvent → State transformation
CubitSimplified Bloc
BlocBuilderRebuild UI on state change
BlocListenerSide effects
BlocConsumerBuilder + Listener
Last updated on