BLoC (viết tắt của Business Logic Component) là một mẫu kiến trúc (pattern) được sử dụng để quản lý trạng thái trong ứng dụng Flutter. Nó giúp tách biệt logic xử lý nghiệp vụ (business logic) khỏi giao diện người dùng (UI), nhằm tạo ra các ứng dụng có cấu trúc rõ ràng, dễ bảo trì và mở rộng.
BLoC pattern trong Flutter dựa trên việc sử dụng Streams để quản lý và điều phối dữ liệu giữa UI và logic nghiệp vụ. Các luồng dữ liệu này cung cấp cơ chế để:
BLoC hoạt động dựa trên 3 yếu tố chính:
Stream
để lắng nghe và phát ra các thay đổi của trạng thái.Quy trình hoạt động của BLoC:
Một BLoC thường được tổ chức theo cấu trúc như sau:
Dưới đây là một ví dụ cơ bản để minh họa cách BLoC hoạt động trong ứng dụng Flutter, thông qua một ứng dụng đếm số đơn giản.
Đầu tiên, chúng ta cần định nghĩa các sự kiện (event). Ứng dụng đếm số sẽ có hai sự kiện cơ bản: Increment (tăng số) và Decrement (giảm số).
// counter_event.dart abstract class CounterEvent {} class IncrementEvent extends CounterEvent {} class DecrementEvent extends CounterEvent {}
Sau đó, chúng ta sẽ định nghĩa trạng thái (state) của bộ đếm số, trong trường hợp này chỉ là giá trị hiện tại của bộ đếm.
// counter_state.dart class CounterState { final int counterValue; CounterState({required this.counterValue}); }
Bây giờ, chúng ta sẽ tạo BLoC để xử lý các sự kiện và phát ra các trạng thái tương ứng. Khi nhận được IncrementEvent
, giá trị bộ đếm sẽ tăng lên; ngược lại, khi nhận được DecrementEvent
, giá trị sẽ giảm xuống.
// counter_bloc.dart import 'dart:async'; import 'counter_event.dart'; import 'counter_state.dart'; class CounterBloc { // Giá trị khởi tạo của CounterState CounterState _state = CounterState(counterValue: 0); // StreamController để quản lý các sự kiện final _eventController = StreamController<CounterEvent>(); // StreamController để phát trạng thái mới final _stateController = StreamController<CounterState>(); // Constructor để lắng nghe các sự kiện CounterBloc() { _eventController.stream.listen((event) { if (event is IncrementEvent) { _state = CounterState(counterValue: _state.counterValue + 1); } else if (event is DecrementEvent) { _state = CounterState(counterValue: _state.counterValue - 1); } // Phát ra trạng thái mới _stateController.sink.add(_state); }); } // Phương thức để nhận sự kiện void dispatch(CounterEvent event) { _eventController.sink.add(event); } // Phương thức để lấy Stream của trạng thái Stream<CounterState> get stateStream => _stateController.stream; // Đóng Stream khi không sử dụng void dispose() { _eventController.close(); _stateController.close(); } }
Bây giờ, chúng ta sẽ kết nối BLoC với UI để hiển thị và điều khiển bộ đếm.
// main.dart import 'package:flutter/material.dart'; import 'counter_bloc.dart'; import 'counter_event.dart'; import 'counter_state.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CounterScreen(), ); } } class CounterScreen extends StatefulWidget { @override _CounterScreenState createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { final CounterBloc _bloc = CounterBloc(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Counter with BLoC'), ), body: Center( child: StreamBuilder<CounterState>( stream: _bloc.stateStream, initialData: CounterState(counterValue: 0), builder: (context, snapshot) { if (snapshot.hasData) { return Text('Counter: ${snapshot.data!.counterValue}', style: TextStyle(fontSize: 24)); } else { return CircularProgressIndicator(); } }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () { _bloc.dispatch(IncrementEvent()); }, child: Icon(Icons.add), ), SizedBox(height: 10), FloatingActionButton( onPressed: () { _bloc.dispatch(DecrementEvent()); }, child: Icon(Icons.remove), ), ], ), ); } @override void dispose() { _bloc.dispose(); super.dispose(); } }
BLoC
và cập nhật giao diện khi trạng thái thay đổi.IncrementEvent
hoặc DecrementEvent
sẽ được phát ra thông qua BLoC, và BLoC sẽ xử lý để phát ra trạng thái mới.flutter_bloc
Thư viện flutter_bloc
là một thư viện chính thức hỗ trợ việc triển khai BLoC một cách dễ dàng hơn và có nhiều tính năng hữu ích. Bạn có thể sử dụng flutter_bloc
để quản lý logic nghiệp vụ mà không cần phải tự quản lý Streams và StreamControllers.
Cài đặt thư viện:
dependencies: flutter_bloc: ^8.0.0
Ví dụ với flutter_bloc:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: BlocProvider( create: (_) => CounterBloc(), child: CounterScreen(), ), ); } } // Bloc class CounterBloc extends Cubit<int> { CounterBloc() : super(0); void increment() => emit(state + 1); void decrement() => emit(state - 1); } // UI class CounterScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Counter with Bloc'), ), body: Center( child: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Text('$count', style: TextStyle(fontSize: 24)); }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () => context.read<CounterBloc>().increment(), child: Icon(Icons.add), ), SizedBox(height: 10), FloatingActionButton( onPressed: () => context.read<CounterBloc>().decrement(), child: Icon(Icons.remove), ), ], ), ); } }
Thư viện flutter_bloc
giúp giảm bớt sự phức tạp khi làm việc với Streams, cho phép bạn dễ dàng quản lý trạng thái và sự kiện trong ứng dụng.
Bloc Pattern giúp tổ chức mã nguồn tốt hơn trong các ứng dụng Flutter bằng cách tách biệt hoàn toàn logic nghiệp vụ và giao diện. Điều này làm cho ứng dụng dễ bảo trì, kiểm thử và mở rộng khi ứng dụng ngày càng phát triển.