Flutter BLoC 状态管理
FreeGuideOnline
最新
2026-07-12
yaml dependencies: flutter_bloc: ^8.1.3 equatable: ^2.0.5 # 简化状态比较
执行 `flutter pub get`,即可开始。
## 第一个计数器 BLoC
### 定义事件和状态
使用 `equatable` 可以让状态对象更方便比较,避免不必要的界面重绘。
```dart
// counter_event.dart
import 'package:equatable/equatable.dart';
abstract class CounterEvent extends Equatable {
@override
List<Object> get props => [];
}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// counter_state.dart
import 'package:equatable/equatable.dart';
class CounterState extends Equatable {
final int count;
const CounterState({required this.count});
// 提供初始状态工厂方法
factory CounterState.initial() => const CounterState(count: 0);
@override
List<Object> get props => [count];
}
实现 BLoC
// counter_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState.initial()) {
on<Increment>((event, emit) {
emit(CounterState(count: state.count + 1));
});
on<Decrement>((event, emit) {
emit(CounterState(count: state.count - 1));
});
}
}
关键点:
- 构造函数中通过
on<Event>注册事件处理器。 emit发出新状态,状态不可变,所以每次创建新实例。state属性自动持有当前状态。
将 BLoC 注入 Widget 树
使用 BlocProvider 在需要访问 BLoC 的 Widget 子树顶部提供实例。
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_page.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (context) => CounterBloc(),
child: CounterPage(),
),
);
}
}
界面响应状态变化
BlocBuilder – 重建 UI
BlocBuilder 监听 BLoC 的状态流,当状态变化时重新执行 builder 回调。
// counter_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_state.dart';
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('BLoC Counter')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'${state.count}',
style: TextStyle(fontSize: 48),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'inc',
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
heroTag: 'dec',
onPressed: () => context.read<CounterBloc>().add(Decrement()),
child: Icon(Icons.remove),
),
],
),
);
}
}
通过 context.read<CounterBloc>() 获取 BLoC 并添加事件,无需监听其状态变化。
BlocListener – 副作用处理
执行一次性的操作,如弹出 SnackBar、导航等,用 BlocListener。
BlocListener<CounterBloc, CounterState>(
listener: (context, state) {
if (state.count == 5) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('达到5!')),
);
}
},
child: ... // 子 Widget
)
BlocConsumer – 同时监听与构建
BlocConsumer 合并了 BlocBuilder 和 BlocListener,避免嵌套。
BlocConsumer<CounterBloc, CounterState>(
listener: (context, state) {
if (state.count == -1) {
showDialog(...);
}
},
builder: (context, state) {
return Text('${state.count}');
},
)
引入仓库层 解耦数据源
实际项目中,BLoC 不应直接操作 API 或数据库,需要引入仓库 (Repository)。
// counter_repository.dart
class CounterRepository {
int _value = 0;
Future<int> fetchCount() async {
await Future.delayed(Duration(seconds: 1));
return _value;
}
Future<void> saveCount(int value) async {
await Future.delayed(Duration(milliseconds: 500));
_value = value;
}
}
修改 BLoC 使其依赖 Repository:
class CounterBloc extends Bloc<CounterEvent, CounterState> {
final CounterRepository repository;
CounterBloc({required this.repository}) : super(CounterState.initial()) {
on<LoadCounter>(_onLoad);
on<Increment>(_onIncrement);
}
Future<void> _onLoad(LoadCounter event, Emitter<CounterState> emit) async {
final count = await repository.fetchCount();
emit(CounterState(count: count));
}
Future<void> _onIncrement(Increment event, Emitter<CounterState> emit) async {
final newCount = state.count + 1;
await repository.saveCount(newCount);
emit(CounterState(count: newCount));
}
}
通过 RepositoryProvider 在 Widget 树顶层提供 Repository,BLoC 通过依赖注入获取。
RepositoryProvider(
create: (context) => CounterRepository(),
child: BlocProvider(
create: (context) => CounterBloc(
repository: context.read<CounterRepository>(),
),
child: CounterPage(),
),
)
多 BLoC 协作
大型应用常有多个 BLoC 需要通信。可通过 BlocListener 监听其他 BLoC,或在 BLoC 内部订阅流。
class CartBloc extends Bloc<CartEvent, CartState> {
final ProductBloc productBloc; // 注入依赖
late final StreamSubscription productSubscription;
CartBloc({required this.productBloc}) : super(CartState.initial()) {
productSubscription = productBloc.stream.listen((state) {
// 当 ProductBloc 状态变化时更新购物车
if (state.status == ProductStatus.added) {
add(AddItem(state.product));
}
});
}
@override
Future<void> close() {
productSubscription.cancel();
return super.close();
}
}
状态追踪与调试
开启 BlocObserver 观察所有 BLoC 的变化:
void main() {
Bloc.observer = SimpleBlocObserver();
runApp(MyApp());
}
class SimpleBlocObserver extends BlocObserver {
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('${bloc.runtimeType} $change');
}
}
测试 BLoC
BLoC 的测试极其简单,不依赖 Flutter 环境:
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('CounterBloc', () {
late CounterBloc bloc;
late MockCounterRepository repository; // 使用 mockito 模拟
setUp(() {
repository = MockCounterRepository();
bloc = CounterBloc(repository: repository);
});
blocTest<CounterBloc, CounterState>(
'emits [CounterState(1)] when Increment is added',
build: () => bloc,
act: (bloc) => bloc.add(Increment()),
expect: () => [CounterState(count: 1)],
);
tearDown(() => bloc.close());
});
}