Bỏ qua để đến nội dung

🧱 BLoC Pattern

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

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

abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterState {
final int count;
const CounterState(this.count);
}
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));
});
}
}

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

BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
)
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('+'),
),
],
);
}
}

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(),
)

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

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

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

Use case Recommendation
Simple state setState hoặc Provider
Medium complexity Cubit hoặc Riverpod
Complex, event-driven BLoC
Large team, enterprise BLoC

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