源码地址
flutter_bloc
是基于 BLoC(Business Logic Component)模式的 Flutter 状态管理库,它封装了 bloc
package,帮助我们更清晰地组织业务逻辑与 UI 的分离。核心思想是 事件驱动 和 状态响应。
🧠 原理简介
1. 核心概念
- Event(事件):用户的输入或其他外部触发,比如按钮点击。
- State(状态):界面状态的表现,比如加载中、成功、失败。
- Bloc(逻辑组件):接收事件 -> 处理逻辑 -> 发出新状态。
流程图如下:
UI → Bloc.add(Event) → Bloc → emit(State) → UI rebuild
🛠️ 如何使用
1. 安装依赖
dependencies:flutter_bloc: ^8.1.3 # 检查 pub.dev 上的最新版本
2. 创建 Event & State
// 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);
}
3. 创建 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(0)) {on<Increment>((event, emit) => emit(CounterState(state.count + 1)));on<Decrement>((event, emit) => emit(CounterState(state.count - 1)));}
}
4. 在 UI 中使用 Bloc
// 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)),],),);}
}
✅ 总结优点
- 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 实现
bloc.add(Increment());
// 多个文件:event.dart, state.dart, bloc.dart
GetX 实现
controller.count++;
// 只要一个 Controller 类,UI 使用 Obx 自动监听
📌 总结一句话:
Bloc
注重规范和可维护性,适合大型工程;GetX
注重极简和开发效率,适合快速开发。
BuildContext 上的一些扩展方法
在使用 flutter_bloc
(或 provider
)时,BuildContext
上的一些扩展方法是关键,它们帮助你从上下文中访问 Bloc、监听状态、或者进行条件性重建。
以下是常见的几种方法的详细解释和对比:
✅ 1. context.read<T>()
功能:
- 从 widget tree 中 读取 一个
Bloc
(或其他 Provider 提供的对象),不会监听其状态变化。
用途:
- 通常用于触发事件,如
.add(Event)
,因为不需要监听。
示例:
context.read<CounterBloc>().add(Increment());
✅ 2. context.watch<T>()
功能:
- 读取并订阅监听
Bloc
或状态变化。 - 状态变化会导致 widget rebuild。
用途:
- 通常用于 UI 需要根据 Bloc 状态实时刷新。
示例:
final state = context.watch<CounterBloc>().state;
return Text('Count: ${state.count}');
✅ 3. context.select<T, R>(R Function(T value))
功能:
- 读取
Bloc
(或 Provider)中某个字段的值,并监听它的变化。 - 只有当该字段的值发生变化时,才会触发 rebuild。
用途:
- 精细控制重建,避免无谓的 UI 更新。
示例:
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。
示例:
BlocBuilder<CounterBloc, CounterState>(builder: (context, state) => Text('Count: ${state.count}'),
);
✅ 6. BlocListener<T, S>
功能:
- 用于监听状态变化并做一次性副作用操作(如弹窗、跳转)。
示例:
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
🌐 场景描述:请求用户信息
模拟从网络请求一个用户信息(名字、邮箱),展示加载中、成功、失败三种状态。
📦 第一步:定义状态和事件
// 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);
}
⚙️ 第二步:创建 UserBloc
// 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 = 'alice@example.com';emit(UserLoaded(name: name, email: email));} catch (e) {emit(UserError('Failed to fetch user'));}});}
}
🖼️ 第三步:UI 中使用 Bloc
// 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
RepositoryProvider
是 flutter_bloc
提供的一个工具类,作用是将“非 Bloc 的对象(如 Repository、Service、API 客户端)注入到 widget tree 中”,供 Bloc 或其他组件使用。它本质上是一个语义化的 Provider
,目的是让依赖注入更加清晰和语义化。
🧱 1. RepositoryProvider 是什么?
它是 Provider 的语法糖,用于提供数据访问层(Repository),让 Bloc 通过依赖注入获取它。
RepositoryProvider(create: (context) => UserRepository(),child: BlocProvider(create: (context) => UserBloc(context.read<UserRepository>()),child: MyApp(),),
)
🧩 2. 为什么需要 RepositoryProvider?
分离关注点 + 提高可测试性
在 BLoC 架构中,Bloc 只处理业务逻辑,而 Repository 专注于数据来源(数据库/API/本地缓存等)。
- 🧼 清晰的依赖层次结构
- 🔌 可插拔(方便 mock 测试)
- ♻️ 依赖共享(避免重复创建对象)
🧪 3. 实战示例
用户仓库
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(),),),);
}
在 Bloc 中使用
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
class UserRepository {Future<String> fetchUserName() async {await Future.delayed(Duration(seconds: 1));return 'Alice';}
}
settings_repository.dart
class SettingsRepository {bool _darkMode = false;bool get isDarkMode => _darkMode;void toggleDarkMode() => _darkMode = !_darkMode;
}
2️⃣ Bloc 实现(以 User 为例)
user_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));});}
}
3️⃣ 组合使用(main.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
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();}},);}
}
settings_page.dart
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()),);},);}
}
✅ 优点总结
- 清晰的依赖注入结构:Bloc 和 Repository 解耦,易维护
- 复用性强:多个 Bloc 可共用同一个 Repository 实例
- 测试友好:可轻松 mock Repository 进行单元测试
- 清晰语义:RepositoryProvider 表明它不是 Bloc,而是数据提供者
🚀 Bonus:封装更进一步
✅ 已经成功将 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
逻辑更纯粹,同时也方便后续扩展全局错误监听、日志注入等中间件。
💡 可选优化建议
1. 使用 lazy: false
控制立即初始化 Bloc
有些 Bloc(如 SettingsBloc)可能希望 App 启动时就立即初始化:
BlocProvider(lazy: false,create: (context) => SettingsBloc(context.read<SettingsRepository>()),
),
2. 使用 const 构造节省 rebuild
如果子 Widget 和 Bloc 无关,记得加 const
以避免重建。
3. 模块化进一步优化(可选)
可以把 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 项目推荐架构。
源码地址