BLoC / Cubit:Stream 驱动的事件状态机
hardflutterbloccubitstreameventstateflutter-bloc
BLoC 设计哲学
**BLoC(Business Logic Component)**由 Google 工程师提出,核心理念:
- UI 只发送事件,不包含业务逻辑
- 所有状态由 BLoC 计算,UI 订阅状态
- 单向数据流:UI → Event → BLoC → State → UI
flutter pub add flutter_bloc
Cubit:BLoC 的简化版
Cubit 不需要定义事件,直接调用方法改变状态,适合简单场景:
// Cubit:方法直接 emit 新状态
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0); // 初始状态
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
// 使用
BlocProvider(
create: (_) => CounterCubit(),
child: BlocBuilder<CounterCubit, int>(
builder: (context, count) => Text('$count'),
),
)
BLoC:Event 驱动
BLoC 要求先定义 Event 和 State,适合复杂业务逻辑:
// Events
sealed class CounterEvent {}
class CounterIncremented extends CounterEvent {}
class CounterDecremented extends CounterEvent {}
class CounterReset extends CounterEvent {}
// States
sealed class CounterState {}
class CounterInitial extends CounterState { final int value; CounterInitial(this.value); }
class CounterLoading extends CounterState {}
class CounterError extends CounterState { final String message; CounterError(this.message); }
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitial(0)) {
on<CounterIncremented>(_onIncremented);
on<CounterDecremented>(_onDecremented);
on<CounterReset>(_onReset);
}
void _onIncremented(CounterIncremented event, Emitter<CounterState> emit) {
final current = state as CounterInitial;
emit(CounterInitial(current.value + 1));
}
Future<void> _onDecremented(CounterDecremented event, Emitter<CounterState> emit) async {
emit(CounterLoading());
// 模拟异步操作
await Future.delayed(const Duration(milliseconds: 300));
final current = state;
if (current is CounterLoading) {
// 异步操作可能因 state 改变而失效,需判断
}
emit(CounterInitial(0));
}
void _onReset(CounterReset event, Emitter<CounterState> emit) {
emit(CounterInitial(0));
}
}
核心 Widget
BlocProvider —— 注入
// 单个
BlocProvider(
create: (_) => CounterBloc(),
child: MyPage(),
)
// 多个
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => CounterBloc()),
BlocProvider(create: (_) => UserBloc()),
],
child: MyApp(),
)
BlocBuilder —— 订阅重建
BlocBuilder<CounterBloc, CounterState>(
// buildWhen:精细控制何时重建(可选)
buildWhen: (previous, current) => current is CounterInitial,
builder: (context, state) {
return switch (state) {
CounterInitial(:final value) => Text('$value'),
CounterLoading() => const CircularProgressIndicator(),
CounterError(:final message) => Text('Error: $message'),
};
},
)
BlocListener —— 响应副作用
// 不 rebuild,只响应状态变化执行副作用(导航、弹框、Toast)
BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthAuthenticated) {
Navigator.pushReplacementNamed(context, '/home');
} else if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
child: const LoginForm(),
)
// 同时需要 rebuild + 副作用
BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) { /* 副作用 */ },
builder: (context, state) { /* 重建 UI */ },
)
读取 BLoC 实例
// 读取 BLoC,发送事件
context.read<CounterBloc>().add(CounterIncremented());
// 订阅状态(等同于 BlocBuilder)
context.watch<CounterBloc>().state;
// 一次性读取状态(不订阅)
context.read<CounterBloc>().state;
实战:带异步加载的用户列表
// Events
sealed class UserEvent {}
class UserLoadRequested extends UserEvent {}
class UserRefreshRequested extends UserEvent {}
// State
class UserState {
final List<User> users;
final bool isLoading;
final String? error;
const UserState({this.users = const [], this.isLoading = false, this.error});
UserState copyWith({List<User>? users, bool? isLoading, String? error}) =>
UserState(users: users ?? this.users, isLoading: isLoading ?? this.isLoading, error: error);
}
// BLoC
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository _repo;
UserBloc(this._repo) : super(const UserState()) {
on<UserLoadRequested>(_onLoad);
on<UserRefreshRequested>(_onRefresh);
}
Future<void> _onLoad(UserLoadRequested event, Emitter<UserState> emit) async {
emit(state.copyWith(isLoading: true, error: null));
try {
final users = await _repo.fetchUsers();
emit(state.copyWith(users: users, isLoading: false));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
// ...
}
Cubit vs BLoC 选型
| Cubit | BLoC | |
|---|---|---|
| 适合 | 简单 UI 状态、计数器、切换 | 复杂业务、表单、异步流程 |
| 事件追踪 | ❌ | ✅ |
| 可测试性 | ✅ | ✅ |
| 代码量 | 少 | 多 |
建议:从 Cubit 开始,需要事件历史追踪、复杂 Event 映射时升级为 BLoC。