源码地址
flutter_bloc
是基于 BLoC(Business Logic Component)模式的 Flutter 状态管理库,它封装了 bloc
package,帮助我们更清晰地组织业务逻辑与 UI 的分离。核心思想是 事件驱动 和 状态响应。
🧠 原理简介
1. 核心概念
- Event(事件):用户的输入或其他外部触发,比如按钮点击。
- State(状态):界面状态的表现,比如加载中、成功、失败。
- Bloc(逻辑组件):接收事件 -> 处理逻辑 -> 发出新状态。
流程图如下:
UI → Bloc.add(Event) → Bloc → emit(State) → UI rebuild
🛠️ 如何使用
1. 安装依赖
yaml
dependencies:
flutter_bloc: ^8.1.3 # 检查 pub.dev 上的最新版本
2. 创建 Event & State
dart
// counter_event.dart
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
dart
// counter_state.dart
class CounterState {
final int count;
CounterState(this.count);
}
3. 创建 Bloc
dart
// 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)));
}
}
4. 在 UI 中使用 Bloc
dart
// 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 {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: MaterialApp(home: CounterPage()),
);
}
}
class CounterPage extends StatelessWidget {
@override
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)),
],
),
);
}
}
✅ 总结优点
- UI 和业务逻辑分离,利于测试和维护
- 明确的事件-状态流转,代码可读性强
- 强类型 + 自动重建 UI,适用于中大型项目
和 getx 相比有什么优缺点
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
适合你如果:
- 需要严格分离业务逻辑(企业/团队协作项目)
- 希望状态流转可追踪、可测试
- 项目规模较大或可预期扩展
- 你熟悉 Redux / Rx 风格编程
✅ 何时选 GetX
适合你如果:
- 项目较小或是个人开发
- 优先快速开发、上手快
- 喜欢灵活、少写代码
- 同时希望统一路由/DI/状态/国际化等功能
🏁 举例对比:计数器
Bloc 实现
dart
bloc.add(Increment());
// 多个文件:event.dart, state.dart, bloc.dart
GetX 实现
dart
controller.count++;
// 只要一个 Controller 类,UI 使用 Obx 自动监听
📌 总结一句话:
Bloc
注重规范和可维护性,适合大型工程;GetX
注重极简和开发效率,适合快速开发。
BuildContext 上的一些扩展方法
在使用 flutter_bloc
(或 provider
)时,BuildContext
上的一些扩展方法是关键,它们帮助你从上下文中访问 Bloc、监听状态、或者进行条件性重建。
以下是常见的几种方法的详细解释和对比:
✅ 1. context.read<T>()
功能:
- 从 widget tree 中 读取 一个
Bloc
(或其他 Provider 提供的对象),不会监听其状态变化。
用途:
- 通常用于触发事件,如
.add(Event)
,因为不需要监听。
示例:
dart
context.read<CounterBloc>().add(Increment());
✅ 2. context.watch<T>()
功能:
- 读取并订阅监听
Bloc
或状态变化。 - 状态变化会导致 widget rebuild。
用途:
- 通常用于 UI 需要根据 Bloc 状态实时刷新。
示例:
dart
final state = context.watch<CounterBloc>().state;
return Text('Count: ${state.count}');
✅ 3. context.select<T, R>(R Function(T value))
功能:
- 读取
Bloc
(或 Provider)中某个字段的值,并监听它的变化。 - 只有当该字段的值发生变化时,才会触发 rebuild。
用途:
- 精细控制重建,避免无谓的 UI 更新。
示例:
dart
final count = context.select<CounterBloc, int>((bloc) => bloc.state.count);
return Text('Count: $count');
✅ 4. BlocProvider.of<T>(context)
- 等价于
context.read<T>()
- 是旧写法,推荐使用
context.read<T>()
更简洁。
✅ 5. BlocBuilder<T, S>
功能:
- 监听
Bloc<T>
的状态S
并根据状态变化 rebuild UI。
示例:
dart
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) => Text('Count: ${state.count}'),
);
✅ 6. BlocListener<T, S>
功能:
- 用于监听状态变化并做一次性副作用操作(如弹窗、跳转)。
示例:
dart
BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
Navigator.pushNamed(context, '/home');
}
},
child: ...
);
✅ 7. BlocConsumer<T, S>
- 相当于
BlocBuilder
+BlocListener
的组合。 - 同时用于 build UI 和执行副作用。
🔁 方法使用场景对比表
方法名 | 是否 rebuild | 是否监听状态变化 | 用途 |
---|---|---|---|
read<T>() |
❌ | ❌ | 获取 Bloc 实例、添加事件 |
watch<T>() |
✅ | ✅ | 获取 Bloc 状态,状态变就重建 |
select<T, R>() |
✅ (条件) | ✅ (某字段变) | 精细控制重建,提高性能 |
BlocBuilder |
✅ | ✅ | 渲染 UI |
BlocListener |
❌ | ✅ | 处理一次性副作用 |
BlocConsumer |
✅ | ✅ | UI 和副作用一起处理 |
使用场景举例子
好的,我们用一个异步 API 请求的完整例子 来演示 flutter_bloc
中各类常用方法的实际应用,包括:
context.read
context.watch
context.select
BlocBuilder
BlocListener
BlocConsumer
🌐 场景描述:请求用户信息
模拟从网络请求一个用户信息(名字、邮箱),展示加载中、成功、失败三种状态。
📦 第一步:定义状态和事件
dart
// user_event.dart
abstract class UserEvent {}
class FetchUser extends UserEvent {}
dart
// 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);
}
⚙️ 第二步:创建 UserBloc
dart
// 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'));
}
});
}
}
🖼️ 第三步:UI 中使用 Bloc
dart
// 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 {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => UserBloc(),
child: UserView(),
);
}
}
class UserView extends StatelessWidget {
@override
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
示例(只监听状态)
dart
final userState = context.watch<UserBloc>().state;
if (userState is UserLoaded) {
print(userState.name);
}
context.select
示例(只监听 name 变化)
dart
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
RepositoryProvider
是 flutter_bloc
提供的一个工具类,作用是将"非 Bloc 的对象 (如 Repository、Service、API 客户端)注入到 widget tree 中",供 Bloc 或其他组件使用。它本质上是一个语义化的 Provider
,目的是让依赖注入更加清晰和语义化。
🧱 1. RepositoryProvider 是什么?
它是 Provider 的语法糖,用于提供数据访问层(Repository),让 Bloc 通过依赖注入获取它。
dart
RepositoryProvider(
create: (context) => UserRepository(),
child: BlocProvider(
create: (context) => UserBloc(context.read<UserRepository>()),
child: MyApp(),
),
)
🧩 2. 为什么需要 RepositoryProvider?
分离关注点 + 提高可测试性
在 BLoC 架构中,Bloc 只处理业务逻辑,而 Repository 专注于数据来源(数据库/API/本地缓存等)。
- 🧼 清晰的依赖层次结构
- 🔌 可插拔(方便 mock 测试)
- ♻️ 依赖共享(避免重复创建对象)
🧪 3. 实战示例
用户仓库
dart
class UserRepository {
Future<String> fetchUserName() async {
await Future.delayed(Duration(seconds: 1));
return 'Alice';
}
}
注入层级
dart
void main() {
runApp(
RepositoryProvider(
create: (context) => UserRepository(),
child: BlocProvider(
create: (context) => UserBloc(context.read<UserRepository>()),
child: MyApp(),
),
),
);
}
在 Bloc 中使用
dart
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));
});
}
}
✅ 4. 优点总结
优点 | 说明 |
---|---|
👓 语义清晰 | 明确指出这是 Repository,不是 Bloc |
♻️ 对象共享 | 上层创建、下层 Bloc 可复用 |
🧪 易测试 | Bloc 接收参数,可以注入 mock |
🧩 解耦结构 | Bloc 不负责创建数据层 |
⚠️ 5. 注意事项 / 潜在缺点
缺点或注意点 | 说明 |
---|---|
⚠️ 滥用嵌套 | BlocProvider 和 RepositoryProvider 层级深容易混乱,建议封装为模块 |
⚠️ 生命周期问题 | 如果作用域太小,Bloc 中引用的 Repository 可能会被提前销毁 |
⚠️ 多仓库依赖复杂度提升 | Bloc 依赖多个 Repository 时,构造函数会变长,可考虑封装为 Service 层或使用 DI 工具(如 get_it) |
📌 何时使用 RepositoryProvider?
✅ 使用场景:
- 需要复用或共享 Repository 实例(如网络请求、数据库访问)
- Bloc 不应该直接创建依赖对象
- 需要单元测试 Bloc 时方便注入 mock
❌ 不建议使用场景:
- 简单小项目可以直接在 Bloc 中创建对象(仅限临时用)
- 对象生命周期不需要跨多个组件
一个多仓库 + 多 Bloc 的组合使用场景实例
好的,我们来构建一个中型项目的多仓库 + 多 Bloc + 多 Provider 示例结构,结合 RepositoryProvider
和 BlocProvider
,实现高内聚、低耦合的结构设计。
🧱 示例需求:用户信息 + 设置模块
有两个功能模块:
- 用户模块(User):从 API 获取用户信息
- 设置模块(Settings):管理本地配置(如暗黑模式)
模块依赖:
模块 | 依赖内容 |
---|---|
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
1️⃣ Repository 实现
user_repository.dart
dart
class UserRepository {
Future<String> fetchUserName() async {
await Future.delayed(Duration(seconds: 1));
return 'Alice';
}
}
settings_repository.dart
dart
class SettingsRepository {
bool _darkMode = false;
bool get isDarkMode => _darkMode;
void toggleDarkMode() => _darkMode = !_darkMode;
}
2️⃣ Bloc 实现(以 User 为例)
user_bloc.dart
dart
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));
});
}
}
3️⃣ 组合使用(main.dart)
dart
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(),
),
),
);
}
4️⃣ 页面中使用
user_page.dart
dart
class UserPage extends StatelessWidget {
@override
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();
}
},
);
}
}
settings_page.dart
dart
class SettingsPage extends StatelessWidget {
@override
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()),
);
},
);
}
}
✅ 优点总结
- 清晰的依赖注入结构:Bloc 和 Repository 解耦,易维护
- 复用性强:多个 Bloc 可共用同一个 Repository 实例
- 测试友好:可轻松 mock Repository 进行单元测试
- 清晰语义:RepositoryProvider 表明它不是 Bloc,而是数据提供者
🚀 Bonus:封装更进一步
✅ 已经成功将 MultiRepositoryProvider
和 MultiBlocProvider
封装到了 AppProviderWrapper
组件中,结构清晰、职责分离得非常好!
下面是总结和优化建议:
✅ 目前结构回顾
dart
// main.dart
runApp(AppProviderWrapper(child: MyApp()));
dart
// provider_wrapper.dart
class AppProviderWrapper extends StatelessWidget {
final Widget child;
const AppProviderWrapper({required this.child});
...
}
这非常适合中大型项目,可以让 main.dart
逻辑更纯粹,同时也方便后续扩展全局错误监听、日志注入等中间件。
💡 可选优化建议
1. 使用 lazy: false
控制立即初始化 Bloc
有些 Bloc(如 SettingsBloc)可能希望 App 启动时就立即初始化:
dart
BlocProvider(
lazy: false,
create: (context) => SettingsBloc(context.read<SettingsRepository>()),
),
2. 使用 const 构造节省 rebuild
如果子 Widget 和 Bloc 无关,记得加 const
以避免重建。
3. 模块化进一步优化(可选)
可以把 Bloc、Repository 的创建封装成方法,增强可维护性:
dart
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
中就可以这么写:
dart
child: MultiBlocProvider(
providers: buildBlocs(context),
child: child,
),
✅ 总结
现在的结构已经非常标准、清晰,完全符合企业级 Flutter 项目推荐架构。
源码地址