源码地址
flutter_bloc
是基于 BLoC(Business Logic Component)模式的 Flutter 状态管理库,它封装了 bloc
package,帮助我们更清晰地组织业务逻辑与 UI 的分离。核心思想是 事件驱动 和 状态响应。
流程图如下:
UI → Bloc.add(Event) → Bloc → emit(State) → UI rebuild
dependencies:
flutter_bloc: ^8.1.3 # 检查 pub.dev 上的最新版本
// counter_event.dart
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// counter_state.dart
class CounterState {
final int count;
CounterState(this.count);
}
// 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(0)) {
on<Increment>((event, emit) => emit(CounterState(state.count + 1)));
on<Decrement>((event, emit) => emit(CounterState(state.count - 1)));
}
}
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: MaterialApp(home: CounterPage()),
);
}
}
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
final bloc = context.read<CounterBloc>();
return Scaffold(
appBar: AppBar(title: Text("BLoC Counter")),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) => Text('Count: ${state.count}', style: TextStyle(fontSize: 30)),
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(onPressed: () => bloc.add(Increment()), child: Icon(Icons.add)),
SizedBox(height: 10),
FloatingActionButton(onPressed: () => bloc.add(Decrement()), child: Icon(Icons.remove)),
],
),
);
}
}
flutter_bloc
和 GetX
是 Flutter 中两种常见的状态管理方案,各有优缺点,适用于不同的场景。下面是二者的详细对比:
flutter_bloc
vs GetX
维度 | flutter_bloc |
GetX |
---|---|---|
设计思想 | 响应式 + 明确事件流转(事件 -> 状态) | 响应式 + 最小 API(简洁直接) |
代码结构 | 规范、结构清晰(Event / State / Bloc) | 极简、灵活(Controller + Observable) |
学习曲线 | 中等偏陡,概念较多 | 非常简单,上手极快 |
样板代码(boilerplate) | 多,需要定义多个类 | 极少,一个控制器基本搞定 |
可维护性(大型项目) | 高,适合多人协作和规范化开发 | 灵活但风险高,依赖命名和使用习惯 |
社区和文档 | 强,广泛用于企业项目,如 Google 官方推荐 | 非官方但很流行,文档充足 |
依赖注入(DI) | 不强制内置,可配合其他包(如 get_it ) |
内置自动依赖注入 |
导航/路由管理 | 依赖其他包(如 go_router ) |
自带强大的路由系统 |
性能 | 非常高效,基于 Stream 和 emit |
极高,Reactive 系统 + 最小重建 |
测试友好性 | 强,适合单元测试/集成测试 | 一般,较多手动控制 |
异步处理 | 基于事件流程,清晰且易测试 | 支持 Future/async,但自由度高可能导致不规范 |
flutter_bloc
适合你如果:
GetX
适合你如果:
bloc.add(Increment());
// 多个文件:event.dart, state.dart, bloc.dart
controller.count++;
// 只要一个 Controller 类,UI 使用 Obx 自动监听
Bloc
注重规范和可维护性,适合大型工程;GetX
注重极简和开发效率,适合快速开发。
在使用 flutter_bloc
(或 provider
)时,BuildContext
上的一些扩展方法是关键,它们帮助你从上下文中访问 Bloc、监听状态、或者进行条件性重建。
以下是常见的几种方法的详细解释和对比:
context.read()
Bloc
(或其他 Provider 提供的对象),不会监听其状态变化。.add(Event)
,因为不需要监听。context.read<CounterBloc>().add(Increment());
context.watch()
Bloc
或状态变化。final state = context.watch<CounterBloc>().state;
return Text('Count: ${state.count}');
context.select(R Function(T value))
Bloc
(或 Provider)中某个字段的值,并监听它的变化。final count = context.select<CounterBloc, int>((bloc) => bloc.state.count);
return Text('Count: $count');
BlocProvider.of(context)
context.read()
context.read()
更简洁。BlocBuilder
Bloc
的状态 S
并根据状态变化 rebuild UI。BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) => Text('Count: ${state.count}'),
);
BlocListener
BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
Navigator.pushNamed(context, '/home');
}
},
child: ...
);
BlocConsumer
BlocBuilder
+ BlocListener
的组合。方法名 | 是否 rebuild | 是否监听状态变化 | 用途 |
---|---|---|---|
read |
❌ | ❌ | 获取 Bloc 实例、添加事件 |
watch |
✅ | ✅ | 获取 Bloc 状态,状态变就重建 |
select |
✅ (条件) | ✅ (某字段变) | 精细控制重建,提高性能 |
BlocBuilder |
✅ | ✅ | 渲染 UI |
BlocListener |
❌ | ✅ | 处理一次性副作用 |
BlocConsumer |
✅ | ✅ | UI 和副作用一起处理 |
好的,我们用一个异步 API 请求的完整例子来演示 flutter_bloc
中各类常用方法的实际应用,包括:
context.read
context.watch
context.select
BlocBuilder
BlocListener
BlocConsumer
模拟从网络请求一个用户信息(名字、邮箱),展示加载中、成功、失败三种状态。
// user_event.dart
abstract class UserEvent {}
class FetchUser extends UserEvent {}
// user_state.dart
abstract class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final String name;
final String email;
UserLoaded({required this.name, required this.email});
}
class UserError extends UserState {
final String message;
UserError(this.message);
}
// user_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'user_event.dart';
import 'user_state.dart';
class UserBloc extends Bloc<UserEvent, UserState> {
UserBloc() : super(UserInitial()) {
on<FetchUser>((event, emit) async {
emit(UserLoading());
await Future.delayed(Duration(seconds: 2)); // 模拟网络延迟
try {
// 模拟 API 成功返回
final name = 'Alice';
final email = '[email protected]';
emit(UserLoaded(name: name, email: email));
} catch (e) {
emit(UserError('Failed to fetch user'));
}
});
}
}
// user_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'user_bloc.dart';
import 'user_event.dart';
import 'user_state.dart';
class UserPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => UserBloc(),
child: UserView(),
);
}
}
class UserView extends StatelessWidget {
Widget build(BuildContext context) {
final bloc = context.read<UserBloc>(); // 获取 Bloc 实例
return Scaffold(
appBar: AppBar(title: Text('User Info')),
body: BlocConsumer<UserBloc, UserState>(
listener: (context, state) {
if (state is UserError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
builder: (context, state) {
if (state is UserInitial) {
return Center(
child: ElevatedButton(
onPressed: () => bloc.add(FetchUser()),
child: Text('Load User'),
),
);
} else if (state is UserLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is UserLoaded) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Name: ${state.name}'),
Text('Email: ${state.email}'),
],
),
);
} else {
return Center(child: Text('Unknown state'));
}
},
),
);
}
}
context.watch
示例(只监听状态)final userState = context.watch<UserBloc>().state;
if (userState is UserLoaded) {
print(userState.name);
}
context.select
示例(只监听 name 变化)final name = context.select<UserBloc, String?>((bloc) {
final state = bloc.state;
return state is UserLoaded ? state.name : null;
});
这个例子完整地展示了:
Bloc
组织异步逻辑context.read
触发事件BlocConsumer
分离 UI 构建和副作用(如错误提示)watch
/ select
实现更细粒度监听RepositoryProvider
是 flutter_bloc
提供的一个工具类,作用是将“非 Bloc 的对象(如 Repository、Service、API 客户端)注入到 widget tree 中”,供 Bloc 或其他组件使用。它本质上是一个语义化的 Provider
,目的是让依赖注入更加清晰和语义化。
它是 Provider 的语法糖,用于提供数据访问层(Repository),让 Bloc 通过依赖注入获取它。
RepositoryProvider(
create: (context) => UserRepository(),
child: BlocProvider(
create: (context) => UserBloc(context.read<UserRepository>()),
child: MyApp(),
),
)
分离关注点 + 提高可测试性
在 BLoC 架构中,Bloc 只处理业务逻辑,而 Repository 专注于数据来源(数据库/API/本地缓存等)。
class UserRepository {
Future<String> fetchUserName() async {
await Future.delayed(Duration(seconds: 1));
return 'Alice';
}
}
void main() {
runApp(
RepositoryProvider(
create: (context) => UserRepository(),
child: BlocProvider(
create: (context) => UserBloc(context.read<UserRepository>()),
child: MyApp(),
),
),
);
}
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository userRepository;
UserBloc(this.userRepository) : super(UserInitial()) {
on<FetchUser>((event, emit) async {
emit(UserLoading());
final name = await userRepository.fetchUserName();
emit(UserLoaded(name));
});
}
}
优点 | 说明 |
---|---|
语义清晰 | 明确指出这是 Repository,不是 Bloc |
♻️ 对象共享 | 上层创建、下层 Bloc 可复用 |
易测试 | Bloc 接收参数,可以注入 mock |
解耦结构 | Bloc 不负责创建数据层 |
缺点或注意点 | 说明 |
---|---|
⚠️ 滥用嵌套 | BlocProvider 和 RepositoryProvider 层级深容易混乱,建议封装为模块 |
⚠️ 生命周期问题 | 如果作用域太小,Bloc 中引用的 Repository 可能会被提前销毁 |
⚠️ 多仓库依赖复杂度提升 | Bloc 依赖多个 Repository 时,构造函数会变长,可考虑封装为 Service 层或使用 DI 工具(如 get_it) |
✅ 使用场景:
❌ 不建议使用场景:
好的,我们来构建一个中型项目的多仓库 + 多 Bloc + 多 Provider 示例结构,结合 RepositoryProvider
和 BlocProvider
,实现高内聚、低耦合的结构设计。
模块 | 依赖内容 |
---|---|
UserBloc | 依赖 UserRepository |
SettingsBloc | 依赖 SettingsRepository |
lib/
├── main.dart
├── repositories/
│ ├── user_repository.dart
│ └── settings_repository.dart
├── blocs/
│ ├── user/
│ │ ├── user_bloc.dart
│ │ ├── user_event.dart
│ │ └── user_state.dart
│ └── settings/
│ ├── settings_bloc.dart
│ ├── settings_event.dart
│ └── settings_state.dart
├── pages/
│ ├── user_page.dart
│ └── settings_page.dart
└── app.dart
class UserRepository {
Future<String> fetchUserName() async {
await Future.delayed(Duration(seconds: 1));
return 'Alice';
}
}
class SettingsRepository {
bool _darkMode = false;
bool get isDarkMode => _darkMode;
void toggleDarkMode() => _darkMode = !_darkMode;
}
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository userRepository;
UserBloc(this.userRepository) : super(UserInitial()) {
on<FetchUser>((event, emit) async {
emit(UserLoading());
final name = await userRepository.fetchUserName();
emit(UserLoaded(name));
});
}
}
void main() {
runApp(
MultiRepositoryProvider(
providers: [
RepositoryProvider(create: (_) => UserRepository()),
RepositoryProvider(create: (_) => SettingsRepository()),
],
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => UserBloc(context.read<UserRepository>()),
),
BlocProvider(
create: (context) => SettingsBloc(context.read<SettingsRepository>()),
),
],
child: MyApp(),
),
),
);
}
class UserPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserInitial) {
return ElevatedButton(
onPressed: () => context.read<UserBloc>().add(FetchUser()),
child: Text("Load User"),
);
} else if (state is UserLoaded) {
return Text('Hello, ${state.name}');
} else {
return CircularProgressIndicator();
}
},
);
}
}
class SettingsPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<SettingsBloc, SettingsState>(
builder: (context, state) {
return SwitchListTile(
title: Text('Dark Mode'),
value: state.isDarkMode,
onChanged: (_) => context.read<SettingsBloc>().add(ToggleTheme()),
);
},
);
}
}
✅ 已经成功将 MultiRepositoryProvider
和 MultiBlocProvider
封装到了 AppProviderWrapper
组件中,结构清晰、职责分离得非常好!
下面是总结和优化建议:
// main.dart
runApp(AppProviderWrapper(child: MyApp()));
// provider_wrapper.dart
class AppProviderWrapper extends StatelessWidget {
final Widget child;
const AppProviderWrapper({required this.child});
...
}
这非常适合中大型项目,可以让 main.dart
逻辑更纯粹,同时也方便后续扩展全局错误监听、日志注入等中间件。
lazy: false
控制立即初始化 Bloc有些 Bloc(如 SettingsBloc)可能希望 App 启动时就立即初始化:
BlocProvider(
lazy: false,
create: (context) => SettingsBloc(context.read<SettingsRepository>()),
),
如果子 Widget 和 Bloc 无关,记得加 const
以避免重建。
可以把 Bloc、Repository 的创建封装成方法,增强可维护性:
List<RepositoryProvider> buildRepositories() => [
RepositoryProvider(create: (_) => UserRepository()),
RepositoryProvider(create: (_) => SettingsRepository()),
];
List<BlocProvider> buildBlocs(BuildContext context) => [
BlocProvider(create: (_) => UserBloc(context.read())),
BlocProvider(create: (_) => SettingsBloc(context.read())),
];
这样 AppProviderWrapper
中就可以这么写:
child: MultiBlocProvider(
providers: buildBlocs(context),
child: child,
),
现在的结构已经非常标准、清晰,完全符合企业级 Flutter 项目推荐架构。
源码地址