Bloc Pattern trong Flutter
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ế để:
- Lắng nghe (listen) dữ liệu từ logic nghiệp vụ (UI sẽ lắng nghe các thay đổi trong dữ liệu).
- Chuyển sự kiện (emit event) từ UI đến BLoC để xử lý.
1. Nguyên lý hoạt động của BLoC
BLoC hoạt động dựa trên 3 yếu tố chính:
- Event: UI hoặc một thành phần nào đó trong ứng dụng phát ra sự kiện (event) để yêu cầu thay đổi trạng thái.
- State: Trạng thái hiện tại của ứng dụng. BLoC sẽ cập nhật trạng thái dựa trên các sự kiện nhận được.
- Stream: Là cơ chế dùng để truyền dữ liệu. BLoC sử dụng
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:
- UI phát ra sự kiện (event) và gửi đến BLoC.
- BLoC nhận sự kiện, xử lý logic nghiệp vụ và cập nhật trạng thái (state) tương ứng.
- BLoC phát ra (emit) trạng thái mới qua Stream để UI lắng nghe và cập nhật.
2. Cấu trúc của một BLoC
Một BLoC thường được tổ chức theo cấu trúc như sau:
- Event: Đại diện cho các sự kiện từ UI (như nhấn nút, thay đổi giá trị) để yêu cầu một hành động từ BLoC.
- State: Mô tả trạng thái của giao diện tại một thời điểm. Khi BLoC xử lý sự kiện, nó sẽ phát ra trạng thái mới.
- Bloc: Nơi xử lý nghiệp vụ (business logic), nhận sự kiện và phát ra trạng thái mới qua Streams.
3. Ví dụ cơ bản về Bloc Pattern
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.
Bước 1: Tạo Event
Đầ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 {}
Bước 2: Tạo State
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ước 3: Tạo Bloc
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ước 4: Kết nối Bloc với UI
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();
}
}
Giải thích:
- StreamBuilder: Đây là widget lắng nghe thay đổi trạng thái từ
BLoC
và cập nhật giao diện khi trạng thái thay đổi.
- Khi bấm các nút, sự kiện
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.
4. Sử dụng thư viện 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.
Kết luận
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.