📚 Flutter 状态管理系列文章目录
源码地址
在 Flutter 开发中,状态管理是构建响应式 UI 的关键。provider
是一个轻量级的、由官方推荐的状态管理库,它封装了 InheritedWidget
的底层逻辑,提供了更加直观易用的 API。
本文将从以下几个方面来讲解 provider
的使用:
- Provider 的核心原理
- 常用 Provider 类型与区别
- 使用实例
- 注意事项与最佳实践
一、Provider 的核心原理
Provider
的底层基于 Flutter 原生的 InheritedWidget
和 ChangeNotifier
。它通过监听数据变化,通知依赖该数据的子 Widget 重新构建。
简要原理图:
Widget Tree
└── ChangeNotifierProvider (创建 & 提供模型)
└── Consumer (监听模型变化,自动 rebuild)
核心 API:
ChangeNotifier
: 状态模型类,支持监听器注册和通知。ChangeNotifierProvider
: 提供状态模型给下层 Widget。Consumer
: 自动订阅模型并响应其变化。context.read
/context.watch
: 用于访问状态。
二、Provider 常见类型
类型 | 功能 | 说明 |
---|---|---|
Provider |
提供一个只读对象 | 不可变数据,如配置项 |
ChangeNotifierProvider |
提供带通知能力的对象 | 最常用,适合多数状态管理场景 |
FutureProvider |
提供一个异步计算结果 | 适合加载远程数据 |
StreamProvider |
提供一个 Stream 数据流 | 比如监听 WebSocket 消息 |
MultiProvider |
组合多个 Provider | 结构更清晰,避免嵌套 |
三、使用实例:
添加依赖
dependencies:
flutter:
sdk: flutter
provider: ^6.1.0 # 最新版本可在 pub.dev 查看
一个计数器 App
// 1. 创建模型类
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知 UI 更新
}
}
// 2. 使用 ChangeNotifierProvider 包裹根组件
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
),
);
}
// 3. 在 UI 中使用
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
// 使用 Consumer 监听变化
return Scaffold(
appBar: AppBar(title: Text('Provider 示例')),
body: Center(
child: Consumer<CounterModel>(
builder: (_, model, __) => Text('Count: ${model.count}', style: TextStyle(fontSize: 24)),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterModel>().increment(),
child: Icon(Icons.add),
),
);
}
}
当然可以,下面是第二部分「常见 Provider 类型与区别」中每种类型的独立代码示例,帮助你快速理解每种 Provider 的使用场景和方式。
Provider 常见类型 + 每种类型的代码示例
1. Provider<T>
:提供只读、不可变对象(不监听变化)
适用于静态数据,如配置项、常量。
void main() {
runApp(
Provider<String>.value(
value: "App Title",
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final title = Provider.of<String>(context); // 不可变,读取即可
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('Welcome')),
),
);
}
}
2. ChangeNotifierProvider<T>
:最常用,可变数据 + 通知 UI 更新
适用于需要 UI 响应变化的状态管理。
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(home: CounterPage());
}
}
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer<CounterModel>(
builder: (_, model, __) => Text('Count: ${model.count}'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterModel>().increment(),
child: Icon(Icons.add),
),
);
}
}
3. FutureProvider<T>
:处理异步操作(Future),如初始化数据、请求接口
适用于首次进入页面时加载远程数据。
Future<String> fetchUserName() async {
await Future.delayed(Duration(seconds: 2));
return "Alice";
}
void main() {
runApp(
FutureProvider<String>(
create: (_) => fetchUserName(),
initialData: "Loading...",
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final name = Provider.of<String>(context);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('FutureProvider 示例')),
body: Center(child: Text('Hello, $name')),
),
);
}
}
4. StreamProvider<T>
:处理实时数据流(Stream),如 WebSocket、倒计时
适用于需要持续响应数据变化的场景。
Stream<int> countdownStream() {
return Stream.periodic(Duration(seconds: 1), (x) => 10 - x).take(11);
}
void main() {
runApp(
StreamProvider<int>(
create: (_) => countdownStream(),
initialData: 10,
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final count = Provider.of<int>(context);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('StreamProvider 示例')),
body: Center(child: Text('倒计时: $count')),
),
);
}
}
5. MultiProvider
:合并多个 Provider,结构更清晰
适用于需要管理多个状态模型的项目。
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class ThemeModel extends ChangeNotifier {
bool _dark = false;
bool get isDark => _dark;
void toggleTheme() {
_dark = !_dark;
notifyListeners();
}
}
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CounterModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final isDark = context.watch<ThemeModel>().isDark;
return MaterialApp(
theme: isDark ? ThemeData.dark() : ThemeData.light(),
home: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
final counterModel = context.watch<CounterModel>();
final themeModel = context.watch<ThemeModel>();
return Scaffold(
appBar: AppBar(
title: Text('MultiProvider 示例'),
actions: [
IconButton(
icon: Icon(themeModel.isDark ? Icons.dark_mode : Icons.light_mode),
onPressed: () => themeModel.toggleTheme(),
),
],
),
body: Center(
child: Text(
'计数器:${counterModel.count}',
style: TextStyle(fontSize: 28),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counterModel.increment(),
child: Icon(Icons.add),
),
);
}
}
✅ 总结
类型 | 适合场景 | 是否响应变化 |
---|---|---|
Provider | 常量/配置/单例 | 否 |
ChangeNotifierProvider | 组件状态管理 | ✅ 是 |
FutureProvider | 异步初始化 | ✅ 是 |
StreamProvider | 实时流数据 | ✅ 是 |
MultiProvider | 多个 Provider 合并 | — |
局部 Provider 的使用场景
局部 Provider
的使用,指的是只在某个界面或某个 widget 树中注入和使用状态模型,而不是在整个 app 顶部(main()
)注入。这样可以实现局部状态隔离、按需创建与释放资源,非常适合临时状态、弹窗状态、对话框状态等场景。
- 某个页面特有的状态(如分页控制器、表单状态)
- 某个 widget(组件)独有的状态(如切换、Tab 状态)
- 避免全局状态污染,提高组件复用性
- 状态不需要跨页面共享
✅ 局部 Provider 的基本用法语法
你可以像这样,在 Widget 树中局部包裹:
Widget build(BuildContext context) {
return Provider<Type>(
create: (_) => YourModel(),
child: YourWidget(),
);
}
✅ 完整示例:局部计数器(仅在一个页面内使用)
模型类(局部使用,依然可以用 ChangeNotifier
)
class LocalCounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
页面使用(在 build
中注入局部 Provider)
class LocalCounterPage extends StatelessWidget {
Widget build(BuildContext context) {
return ChangeNotifierProvider<LocalCounterModel>(
create: (_) => LocalCounterModel(),
child: Scaffold(
appBar: AppBar(title: Text("局部 Provider 示例")),
body: Center(
child: Consumer<LocalCounterModel>(
builder: (_, model, __) => Text(
'局部计数:${model.count}',
style: TextStyle(fontSize: 24),
),
),
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: () => context.read<LocalCounterModel>().increment(),
child: Icon(Icons.add),
),
),
),
);
}
}
✅ 仅在 Widget 中使用 Provider(非页面)
我们可以在任意组件中使用局部 Provider,例如一个局部开关组件:
class ToggleModel extends ChangeNotifier {
bool _on = false;
bool get isOn => _on;
void toggle() {
_on = !_on;
notifyListeners();
}
}
class ToggleSwitch extends StatelessWidget {
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ToggleModel(),
child: Consumer<ToggleModel>(
builder: (_, model, __) => Switch(
value: model.isOn,
onChanged: (_) => model.toggle(),
),
),
);
}
}
使用方式:
class DemoPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("局部组件 Provider")),
body: Center(child: ToggleSwitch()),
);
}
}
✅ 局部 Provider 的注意事项
注意事项 | 说明 |
---|---|
生命周期 | Widget dispose 时,Provider 对象也会被释放(create 的值) |
不要嵌套 Provider 重复提供相同类型 | 否则 context.read/watch 可能拿到错误的实例 |
避免在 context 未挂载时使用 Provider | 可以用 Builder 或将 Provider 放在 widget 层级之上 |
Builder 分离作用域 | FloatingActionButton/AlertDialog 中使用 Provider 时,推荐使用 Builder 包裹以获取正确的 context |
✅ 什么时候使用局部 Provider?
局部 Provider 更适合 | 全局 Provider 更适合 |
---|---|
页面级局部状态 | 多页面共享状态 |
弹窗、局部 UI 控件 | 用户登录状态、主题状态 |
数据无需持久共享 | 持久数据或跨组件通信 |
四、使用 Provider 的注意事项
1. 避免重复创建模型
// ❌ 每次 build 都会创建新的模型
Provider(create: (_) => CounterModel())
// ✅ 应放在 widget tree 顶部,只创建一次
ChangeNotifierProvider(create: (_) => CounterModel())
2. 使用 .read
和 .watch
时机不同
context.read<T>()
:用于只读取,不监听。比如点击事件。context.watch<T>()
:监听并 rebuild。适合在 build 方法中使用。Consumer<T>
:更细粒度的监听,避免整个 widget 重建。
onPressed: () => context.read<CounterModel>().increment(); // ✅
3. listen: false
用于只获取,不监听
Provider.of<CounterModel>(context, listen: false).increment();
4. 避免嵌套过深
使用 MultiProvider
合并多个 provider:
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CounterModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
],
child: MyApp(),
)
五、Provider 与 InheritedWidget 对比原理
Provider 本质是对 InheritedWidget
的封装。以下是它的底层机制简要说明:
class MyInheritedWidget extends InheritedWidget {
final int count;
MyInheritedWidget({required this.count, required Widget child})
: super(child: child);
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return count != oldWidget.count;
}
static MyInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
而 Provider 做了以下封装:
- 使用
ChangeNotifier
自动管理监听器列表; - 自动调用
notifyListeners()
时触发依赖更新; - 简化注册与访问逻辑,提升开发效率。
六、Provider 生命周期
在 Flutter 中,Provider
的生命周期主要与其注入的 Widget 树结构紧密相关。理解 Provider
的生命周期非常重要,尤其在涉及资源释放(如 dispose()
)、避免内存泄露以及按需创建对象等场景下。
🔍 a、Provider 生命周期简要概览
生命周期阶段(以 ChangeNotifierProvider
为例):
创建阶段
在 widget 树构建时执行create
,生成并提供状态对象。使用阶段
状态对象被子树中的 widget 访问(如context.watch()
、Consumer
)并响应变化。销毁阶段
当Provider
所在的 widget 被移除时(比如页面被 pop),Provider 会自动 dispose 提供的对象(前提是它由create:
创建)。
✅ b、图解生命周期流程
构建 Widget Tree
↓
Provider.create(context)
↓
UI 使用状态对象
(context.watch(), context.read(), Consumer)
↓
状态变化触发 notifyListeners → 自动 rebuild
↓
Widget Tree 被销毁
↓
Provider 自动调用对象的 dispose()
🧪 c、代码验证:生命周期日志
示例模型类:
class DemoModel extends ChangeNotifier {
DemoModel() {
print('🟢 DemoModel created');
}
void doSomething() {
notifyListeners();
}
void dispose() {
print('🔴 DemoModel disposed');
super.dispose();
}
}
页面中使用局部 Provider:
class DemoPage extends StatelessWidget {
Widget build(BuildContext context) {
return ChangeNotifierProvider<DemoModel>(
create: (_) => DemoModel(),
child: Scaffold(
appBar: AppBar(title: Text("生命周期测试")),
body: Center(
child: Consumer<DemoModel>(
builder: (_, model, __) => ElevatedButton(
onPressed: () => model.doSomething(),
child: Text('触发更新'),
),
),
),
),
);
}
}
返回上一页(或移除该页面)时,控制台输出:
🟢 DemoModel created
🔴 DemoModel disposed
⚠️ d、注意事项
情况 | 生命周期说明 |
---|---|
使用 create: 提供对象 |
Provider 会自动调用 dispose() |
使用 .value 构造 |
你必须手动管理对象的生命周期 |
在 main() 中提供全局 Provider |
生命周期等于整个 App 生命周期 |
页面级 Provider | 页面关闭时自动销毁对象 |
Widget 级 Provider | Widget 移除时销毁对象 |
❗ .value
和 create:
生命周期对比
// ✅ 自动 dispose(推荐)
ChangeNotifierProvider(
create: (_) => MyModel(),
child: ...,
);
// ❌ 不会自动 dispose(仅适用于已存在对象)
ChangeNotifierProvider.value(
value: existingModel,
child: ...,
);
✅ e、结合 Flutter Navigator 的 Provider 生命周期
- 使用
Navigator.push
进入一个页面,该页面内部用Provider
创建状态对象; - 页面关闭(pop)时,对应的
Provider
会 dispose; - 若用在
showDialog
/BottomSheet
这类临时弹出组件上,其生命周期更短,更适合用局部 Provider 管理资源。
🎯 f、最佳实践
建议 | 原因 |
---|---|
优先使用 create: 构造方式 |
自动管理生命周期,避免内存泄露 |
页面局部状态使用局部 Provider | 隔离、轻量、高内聚 |
避免在长生命周期中频繁新建 Provider | 性能更优 |
使用 dispose() 释放资源 |
如:Controller、Stream、Timer |
七、Provider.value
和 Provider.create
的区别
这是一个非常重要的问题,Provider.value
和 Provider.create
的区别不仅影响状态的生命周期管理,还决定了你在不同场景下的正确使用方式。下面我来详细解释它们的区别、使用场景,并结合代码示例说明。
create:
与 .value:
的根本区别
比较项 | create: |
.value: |
---|---|---|
用途 | 创建一个新对象 | 使用已有的对象 |
是否自动 dispose | ✅ 是(推荐) | ❌ 否(需你手动处理) |
构造时机 | 首次插入 widget 树时执行 | 立即传入,保持外部状态 |
典型使用场景 | 新页面、新状态、生命周期受控 | 重复使用已有对象,如 ListView.builder 中 |
代码对比示例
🎯 使用 create:
(推荐,一般使用场景)
ChangeNotifierProvider<MyModel>(
create: (_) => MyModel(), // 每次创建新的 MyModel 实例
child: MyPage(),
);
- 创建的是新对象;
- 生命周期绑定到 Provider;
- 页面销毁时自动调用
dispose()
; - 推荐:在页面、widget 初始化时使用。
🎯 使用 .value:
(注意:不会自动销毁)
final myModel = MyModel(); // 已有对象
ChangeNotifierProvider<MyModel>.value(
value: myModel,
child: MyPage(),
);
- 使用的是已有对象(外部已创建);
- Provider 不管理其销毁;
- 推荐:在对象复用/传递已有状态时使用,比如在
ListView.builder
中。
.value
场景示例:在 ListView
中复用对象
class ItemModel extends ChangeNotifier {
final String title;
ItemModel(this.title);
}
class ItemWidget extends StatelessWidget {
Widget build(BuildContext context) {
final model = Provider.of<ItemModel>(context);
return ListTile(title: Text(model.title));
}
}
class ListPage extends StatelessWidget {
final items = List.generate(100, (i) => ItemModel('Item $i'));
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) {
return ChangeNotifierProvider.value(
value: items[index], // 已有模型,复用
child: ItemWidget(),
);
},
);
}
}
👆 为什么这里 不能用 create:
?
ListView
会复用 item widget,如果你用create:
,可能会导致:- 状态错乱(旧状态未清除)
- 重复构造(性能浪费)
dispose()
被错误调用
✅ 总结:使用建议
使用方式 | 建议用途 |
---|---|
create: (_) => Model() |
默认推荐,用于页面/局部状态注入 |
.value(value: someModel) |
已存在状态对象时使用,尤其在 widget 复用场景,如列表 |
⚠️ 容易出错的典型用法(反例)
// ❌ 错误用法:create 用于列表
ListView.builder(
itemBuilder: (context, index) {
return ChangeNotifierProvider(
create: (_) => ItemModel('Item $index'), // ❌ 列表滚动会重复创建
child: ItemWidget(),
);
},
);
这会导致频繁构建和不必要的资源浪费。应使用
.value:
并预先构造模型列表。
🎯 小结口诀:
✅ create 新建状态对象,用完自动 dispose;value 复用对象,自管生命周期。
八、总结
Provider 是 Flutter 状态管理中非常易用的方式,但在使用时仍需注意:
- 状态类需继承
ChangeNotifier
; - 使用
read/watch/Consumer
分场景选用; - 避免在 widget build 时不必要地创建模型;
- 多个 Provider 用
MultiProvider
管理; - Provider 底层是 InheritedWidget,但开发体验更佳。
Provider 是一个非常值得掌握的状态管理利器,理解其原理也有助于深入学习其他高级状态管理方案,如 Riverpod、Bloc、GetX 等。