一、Provider 概述
Provider 是 Flutter 官方推荐的状态管理库,它基于 InheritedWidget 实现,通过依赖注入的方式在 Widget 树中高效地共享和管理状态。Provider 的核心优势在于其简单性 和高效性------它只在状态变更时重建依赖该状态的 Widget,而非整个 Widget 树。
二、核心概念
1. ChangeNotifier
ChangeNotifier 是 Flutter SDK 中的一个简单类,用于实现观察者模式。当模型状态发生变化时,调用 notifyListeners() 会通知所有监听者(通常是 UI 组件)进行重建。
示例:购物车模型
dart
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
int get totalPrice => _items.length * 42;
void add(Item item) {
_items.add(item);
notifyListeners(); // 通知监听者状态已更新
}
void removeAll() {
_items.clear();
notifyListeners();
}
}
2. Provider
Provider 是一个 Widget,它负责向子 Widget 树"提供"(暴露)一个值或对象。这个值可以是任何类型的数据。
3. Consumer
Consumer 是一个用于监听状态变化并重建 UI 的 Widget。它会订阅 Provider 中的数据变化,并在数据变更时自动调用其 builder 方法。
三、基本使用步骤
1. 添加依赖
在 pubspec.yaml 中添加 Provider 依赖:
yaml
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0 # 根据Dart版本选择provider版本,可以去pub.dev中查询
运行 flutter pub get 安装包。
2. 创建数据模型(继承 ChangeNotifier)
dart
import 'package:flutter/material.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
3. 在应用顶层提供数据
使用 ChangeNotifierProvider 在 Widget 树顶层提供数据:
dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(), // 创建模型实例
child: MyApp(),
),
);
}
4. 在子 Widget 中消费数据
有三种主要方式消费 Provider 数据:
方式一:使用 Consumer(推荐用于构建 UI)
dart
Consumer<Counter>(
builder: (context, counter, child) {
return Text('Count: ${counter.count}');
},
)
方式二:使用 Provider.of(适合在逻辑中访问数据)
dart
// 获取数据但不监听变化
final counter = Provider.of<Counter>(context, listen: false);
// 获取数据并监听变化
final counter = Provider.of<Counter>(context); // listen: true 是默认值
方式三:使用 Selector(性能优化)
dart
Selector<Counter, int>(
selector: (context, counter) => counter.count, // 只选择特定属性
builder: (context, count, child) {
return Text('Count: $count'); // 仅当 count 变化时重建
},
)
四、不同类型的 Provider
Provider 包提供了多种类型的 Provider 以适应不同场景:
| Provider 类型 | 用途 | 特点 |
|---|---|---|
| Provider | 提供任意类型的值 | 最基本类型,不处理监听逻辑 |
| ChangeNotifierProvider | 提供 ChangeNotifier 对象 | 自动调用 dispose 方法,最常用 |
| ListenableProvider | 提供 Listenable 对象 | ChangeNotifierProvider 的通用版本 |
| ValueListenableProvider | 提供 ValueListenable 对象 | 监听值变化 |
| StreamProvider | 提供 Stream 流 | 自动订阅流,提供最新值 |
| FutureProvider | 提供 Future | 在未来完成时更新依赖项 |
多 Provider 管理
当应用需要多个状态时,使用 MultiProvider:
dart
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
Provider(create: (_) => SomeService()), // 不需要监听的常量或服务
],
child: MyApp(),
),
);
}
依赖型 Provider
当一个模型依赖另一个模型时,使用 ChangeNotifierProxyProvider:
dart
ChangeNotifierProxyProvider<AuthService, UserProfile>(
create: (context) => UserProfile(null),
update: (context, authService, userProfile) =>
UserProfile(authService), // UserProfile 依赖 AuthService
)
五、作用域与 Widget 树位置
1. 作用域原则
Provider 的作用域遵循 "子 Widget 作用域" 原则:数据对其绑定的 Widget 的所有子 Widget 可用。
关键点:
- Provider 应放置在需要使用它的 Widget 之上
- 作用域从 Provider 所在位置开始,向下延伸至所有子 Widget
- 同一类型 Provider 在 Widget 树中可以被覆盖(子树的 Provider 会遮蔽祖先的同类型 Provider)
2. 放置策略
dart
//正确:Provider 放在需要访问它的 Widget 之上
ChangeNotifierProvider(
create: (_) => CartModel(),
child: MyApp(), // MyApp 及其所有子 Widget 都能访问 CartModel
)
//错误:Provider 放在需要它的 Widget 之下
MyApp(
child: ChangeNotifierProvider( // 子 Widget 无法向上查找 Provider
create: (_) => CartModel(),
child: SomeWidget(),
),
)
六、性能优化
1. 精细化重建
- 尽量使用 Consumer/Selector 替代
Provider.of,仅包裹需要重建的部分 - 将 静态内容 作为 child 参数传入,避免不必要的重建
dart
Consumer<CartModel>(
builder: (context, cart, child) {
return Column(
children: [
child!, // 静态标题,不会随 cart 变化而重建
Text('Total: \$${cart.total}'), // 动态部分
],
);
},
child: const HeaderWidget(), // 静态部件
)
2. 使用 Selector 进行属性级监听
dart
Selector<UserModel, String>(
selector: (context, user) => user.name, // 只监听 name 属性
builder: (context, name, child) => Text(name),
)
3. 合理使用 listen 参数
dart
// 在回调中修改状态时不需监听
FloatingActionButton(
onPressed: () {
// listen: false 避免不必要的重建
Provider.of<Counter>(context, listen: false).increment();
},
)
七、最佳实践
1. 模型设计原则
- 单一职责:每个模型只管理相关的状态
- 业务逻辑封装:将相关操作封装在模型方法中
- 不可变数据:尽量使用不可变数据结构,减少意外修改
2. 代码组织
vbnet
lib/
├── models/
│ ├── cart_model.dart
│ ├── user_model.dart
│ └── product_model.dart
├── providers/
│ └── multi_providers.dart
├── screens/
└── widgets/
3. 测试策略
dart
// 模型单元测试(不依赖 Flutter)
test('adding item increases total', () {
final cart = CartModel();
expect(cart.totalPrice, 0);
cart.add(Item('Test'));
expect(cart.totalPrice, 42);
});
八、常见问题与解决方案
1. Provider 找不到错误
arduino
Error: Could not find the correct Provider<CartModel>...
原因 :在 Provider 作用域外尝试访问数据。 解决:确保 Widget 在 Provider 的子树上,或检查 Provider 类型是否匹配。
2. 不必要的重建
原因 :在高层级使用 Consumer 或 listen: true。 解决:
- 将 Consumer 移动到 Widget 树中更低的位置
- 使用 Selector 替代 Consumer
- 在不需要监听的地方使用
listen: false
3. 状态更新但 UI 未刷新
原因 :忘记调用 notifyListeners()。 解决 :确保在状态修改后调用 notifyListeners()。
4. 多个同类型 Provider 冲突
原因 :Widget 树中存在多个同类型 Provider。 解决:明确指定需要哪个 Provider,或重新设计 Provider 结构。
九、完整示例:购物应用
以下是一个完整的购物应用示例,展示了 Provider 的核心用法:
dart
// 1. 数据模型
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
int get totalPrice => _items.length * 42;
void add(Item item) {
_items.add(item);
notifyListeners();
}
void removeAll() {
_items.clear();
notifyListeners();
}
}
// 2. 应用入口
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => CatalogModel()),
],
child: MyApp(),
),
);
}
// 3. 主应用
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '购物应用',
initialRoute: '/',
routes: {
'/': (context) => MyCatalog(),
'/cart': (context) => MyCart(),
},
);
}
}
// 4. 商品列表页面
class MyCatalog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('商品列表')),
body: ListView.builder(
itemCount: catalog.itemCount,
itemBuilder: (context, index) {
final item = catalog.getByIndex(index);
return MyListItem(item);
},
),
);
}
}
// 5. 单个商品项
class MyListItem extends StatelessWidget {
final Item item;
MyListItem(this.item);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(item.imageUrl),
),
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.add_shopping_cart),
onPressed: () {
// 添加商品到购物车
Provider.of<CartModel>(context, listen: false).add(item);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已添加 ${item.name}'),
duration: const Duration(seconds: 1),
),
);
},
),
),
);
}
}
// 6. 购物车页面
class MyCart extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('购物车')),
body: Consumer<CartModel>(
builder: (context, cart, child) {
if (cart.items.isEmpty) {
return const Center(child: Text('购物车为空'));
}
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
trailing: Text('\$42'),
);
},
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'总计: \$${cart.totalPrice}',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
ElevatedButton(
onPressed: () {
// 清空购物车
cart.removeAll();
},
child: const Text('清空购物车'),
),
],
),
),
],
);
},
),
);
}
}
十、进阶话题
1. 自定义 Provider
当内置 Provider 不满足需求时,可以创建自定义 Provider:
dart
class CustomProvider extends ValueNotifier<int> {
CustomProvider() : super(0);
void increment() => value++;
}
// 使用自定义 Provider
ChangeNotifierProvider(
create: (context) => CustomProvider(),
child: ...,
)
2. Provider 与路由结合
dart
// 在不同页面共享状态
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Provider.value(
value: Provider.of<CartModel>(context),
child: CheckoutPage(),
),
),
);
3. Provider 的测试
dart
// Widget 测试
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => CartModel(),
child: MaterialApp(home: MyCart()),
),
);
// 模拟用户交互
final cart = tester.state<CartModel>(find.byType(CartModel));
expect(cart.items.length, 1);
总结
Provider 是 Flutter 生态中最受欢迎的状态管理解决方案之一,它平衡了简单性 和功能性。通过本教程,你应该掌握了:
- 核心概念:ChangeNotifier、Provider、Consumer 的协作机制
- 基本用法:从安装到使用的完整流程
- 高级特性:多 Provider 管理、性能优化、依赖处理
- 最佳实践:代码组织、测试策略、常见问题解决
记住,Provider 的核心思想是将状态提升到需要它的 Widget 之上 ,并通过精细化的重建机制确保应用性能。对于大多数 Flutter 应用,Provider 提供了一个优雅而强大的状态管理方案。
延伸学习:
- 官方文档:Flutter 状态管理
- Provider GitHub 仓库:provider
- 其他状态管理方案对比:Riverpod、Bloc、GetX