一、Provider
1.原理
Provider 本质上是基于 Flutter 的InheritedWidget 实现的,核心思想是数据自上而下传递,形成一个「数据提供者 - 消费者」的树形结构。
2、使用示例
2.1 定义可监听的状态模型(继承 ChangeNotifier) 核心:数据变化时调用 notifyListeners() 通知组件刷新
class LoginStatusModel extends ChangeNotifier {
bool _isLogin = false;
bool get isLogin => _isLogin;
void updateLoginStatus(bool isLogin) {
_isLogin = isLogin;
notifyListeners(); // 关键:通知所有订阅的组件刷新
}
}
2.2 使用 Provider或其子类,包裹 App实例,并将 状态模型实例作为值传递
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => LoginStatusModel(),
child: const MyApp()、
),
);
}
2.3 使用状态数据,在需要监听数据变化的Widget中,使用Provider.of、Consumer获取数据:
@override
Widget build(BuildContext context) {
// 使用 Consumer 监听 CounterModel
return Consumer<LoginStatusModel>(
builder: (context, loginStatus, child) {
return Text('${loginStatus.isLogin}');
},
);
}
}
// 也可以使用 Provider.of() 来获取:
Text('${Provider.of<LoginStatusModel>(context, listen: false).isLogin}')
| 特性 | Provider.of<T>(context) | Consumer<T> | context.watch<T>() (推荐) |
|---|---|---|---|
| 主要用途 | 灵活获取,常用于非 build 方法中 | 在 build 中获取并直接构建子 Widget | 在 build 中获取数据用于逻辑判断或属性赋值 |
| 是否监听变化 | 取决于 listen 参数 (默认 true) | 是 (自动监听) | 是 (自动监听) |
| 代码位置 | 任意位置 (build 内/外,异步方法中) | 只能在 build 方法的 return 树中 | 只能在 build 方法体内 (return 之前) |
| 是否需要 builder | 不需要 | 需要 (builder 回调) | 不需要 |
| 典型场景 | 按钮点击事件、定时器、初始化逻辑 | 需要根据数据动态生成整个 Widget 时 | 需要根据数据决定 Widget 的属性 (颜色、文本) 时 |
| 性能优化 | 可设置 listen: false 避免不必要重绘 | 仅重建 Consumer 及其子节点 | 重建当前 Widget |
A. Provider.of<T>(context)
这是最原始的方法。它的关键在于第二个参数 listen。
-
listen: true(默认) :- 行为:监听数据变化。如果数据变了,当前 Widget 会重建。
- 限制:只能在
build方法中使用(因为重建需要触发 build)。 - 缺点:如果在
build中用默认值,会导致整个父 Widget 重绘,不够精细。
-
listen: false(常用) :- 行为:不监听数据变化。只获取当前的实例对象。
- 场景:在事件回调(如
onPressed)、initState、或者异步方法中调用修改数据的方法(如increment())。 - 优势:不会因为数据变化导致当前 Widget 无谓重绘。
Widget build(BuildContext context) {
// ✅ 获取数据 (自动监听)
final counter = context.watch<CounterModel>();
// 可以在这里做逻辑处理
final color = counter.count > 10 ? Colors.red : Colors.green;
final text = counter.count > 10 ? '太多了!' : '正常';
return Column(
children: [
// 使用处理后的数据
Text(text, style: TextStyle(color: color)),
// 按钮事件 (必须用 read 或 Provider.of(..., listen: false))
ElevatedButton(
onPressed: () => context.read<CounterModel>().increment(),
child: Text('增加'),
)
],
);
}
B. Consumer 是一个 Widget。它的作用是将“获取数据”和“构建 UI”合二为一。
- 特点:它提供了一个
builder函数。只有当数据变化时,只有这个Consumer节点及其子节点会重绘,它的父兄弟节点不会重绘。 - 场景:当你需要根据数据直接返回一个新的 Widget 结构时。
// ✅ 场景:只想让这段文字区域刷新,不影响周围的布局
Consumer<CounterModel>(
builder: (context, counter, child) {
// counter 就是 CounterModel 实例
return Text(
'当前计数: ${counter.count}',
style: TextStyle(fontSize: 24, color: Colors.blue),
);
},
// child 参数可用于优化:传递不变的子组件,避免每次重绘都重建它
// child: Icon(Icons.star),
)
C. context.watch<T>() —— Consumer 的语法糖 (现代推荐)
这是 provider 6.0+ 版本后最推荐的写法。它等价于 Provider.of<T>(context, listen: true),但写法更简洁。
- 特点:直接在
build方法体中使用,返回数据对象。 - 场景:当你需要在
build方法中获取数据,用来计算属性、做条件判断,或者组合多个数据源时。 - 注意:调用
watch的代码所在的 整个 Widget 的build方法 会在数据变化时重跑。如果该 Widget 很大,可能不如Consumer精准。
Widget build(BuildContext context) {
// ✅ 获取数据 (自动监听)
final counter = context.watch<CounterModel>();
// 可以在这里做逻辑处理
final color = counter.count > 10 ? Colors.red : Colors.green;
final text = counter.count > 10 ? '太多了!' : '正常';
return Column(
children: [
// 使用处理后的数据
Text(text, style: TextStyle(color: color)),
// 按钮事件 (必须用 read 或 Provider.of(..., listen: false))
ElevatedButton(
onPressed: () => context.read<CounterModel>().increment(),
child: Text('增加'),
)
],
);
}
总结使用口诀
- 改数据 (按钮/事件) ➡️ 用
read(或of(..., listen: false)) - 显数据 (局部刷新) ➡️ 用
Consumer - 显数据 (简单逻辑) ➡️ 用
watch - 初始化 (生命周期) ➡️ 用
of(..., listen: false)
二、RiverPod
1.原理
简洁表达:
-
中心化管理:通过
ProviderContainer(ProviderScope)统一管理所有状态,状态封装在 Provider 中,脱离 Widget 上下文; -
精准订阅分发:基于
Ref实现 Widget/Provider 对状态的订阅,状态变化时仅通知订阅者,最小化重建; -
无上下文 + 类型安全:解决了传统 Provider 的核心痛点,同时通过静态类型检查提升开发效率。
Ref (通常通过 WidgetRef 在 UI 中使用) 是整个状态管理系统的核心控制器和上下文对象,是widget和provider,Provider和Provider沟通的唯一桥梁。
2、使用示例
- 必须使用
ProviderScope包裹整个应用。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope( // 👈 必须包裹这里
child: MyApp(),
),
);
}
2.1 简单状态:@riverpod (替代 StateProvider)
// counter_provider.dart 文件,供后续订阅分发使用
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 使用 @riverpod 注解,运行 build_runner 后会自动生成 CounterProvider
@riverpod
class Counter extends _$Counter {
@override
int build() {
// 初始值
return 0;
}
// 定义修改状态的方法
void increment() {
state++; // 👈 直接修改 state 属性,自动通知监听者
}
void reset() {
state = 0;
}
}
2.2 复杂状态:AsyncNotifier (替代 FutureProvider + StateNotifier)
用于处理异步操作(如网络请求)并管理复杂状态。这是 Riverpod 最强大的部分。
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 模拟用户模型
class User {
final String name;
final int age;
User({required this.name, required this.age});
}
// 定义 AsyncNotifier
@riverpod
class CurrentUser extends _$CurrentUser {
@override
Future<User> build() async {
// 模拟网络延迟
await Future.delayed(const Duration(seconds: 2));
// 模拟可能发生的错误
// if (someCondition) throw Exception("Failed to load");
return User(name: "Alice", age: 25);
}
// 修改用户信息的方法
Future<void> updateAge(int newAge) async {
state = const AsyncValue.loading(); // 手动设置加载状态
try {
await Future.delayed(const Duration(seconds: 1)); // 模拟 API 调用
final user = state.value!; // 获取旧数据
state = AsyncValue.data(User(name: user.name, age: newAge)); // 更新数据
} catch (e, st) {
state = AsyncValue.error(e, st); // 处理错误
}
}
}
2.3 组合状态:派生数据 (Derived State)
在一个 Provider 中读取另一个 Provider,实现数据联动。
@riverpod
String userNameRef(UserNameRef ref) {
// 监听 CurrentUser Provider
final userAsync = ref.watch(currentUserProvider);
// 处理异步状态
return userAsync.when(
data: (user) => user.name,
loading: () => "加载中...",
error: (_, __) => "加载失败",
);
}
A. 使用 ConsumerWidget (推荐)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 继承 ConsumerWidget
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. 监听简单状态 (Counter)
// ref.watch 会自动订阅,数据变化时重建此 Widget
final count = ref.watch(counterProvider);
// 2. 监听异步状态 (CurrentUser)
final userAsync = ref.watch(currentUserProvider);
return Scaffold(
appBar: AppBar(title: const Text("Riverpod Demo")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 显示异步用户数据
userAsync.when(
data: (user) => Text("你好, ${user.name} (年龄: ${user.age})"),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text("错误: $err"),
),
const SizedBox(height: 20),
// 显示计数
Text("计数: $count", style: const TextStyle(fontSize: 24)),
const SizedBox(height: 20),
// 3. 修改状态 (使用 ref.read 或 ref.notifier)
ElevatedButton(
onPressed: () {
// 方式 A: 直接调用生成的 notifier 方法 (推荐)
ref.read(counterProvider.notifier).increment();
// 方式 B: 如果是 AsyncNotifier
// ref.read(currentUserProvider.notifier).updateAge(26);
},
child: const Text("增加计数"),
),
ElevatedButton(
onPressed: () {
// 触发异步更新
ref.read(currentUserProvider.notifier).updateAge(30);
},
child: const Text("更新用户年龄 (异步)"),
),
],
),
),
);
}
}
B. 在非 Widget 类中使用 (Riverpod 的杀手锏)
由于不依赖 Context,你可以在任何地方(如路由守卫、服务类、甚至 main 函数之后)访问状态。
// 例如:在一个普通的 Dart 类中
class AnalyticsService {
final Ref ref; // 注入 Ref
AnalyticsService(this.ref);
void logCount() {
// 直接读取当前值,不订阅变化 (类似 listen: false)
final currentCount = ref.read(counterProvider);
print("当前计数是: $currentCount");
}
void subscribeToCount() {
// 也可以手动监听变化
ref.listen(counterProvider, (previous, next) {
print("计数从 $previous 变成了 $next");
});
}
}
| 特性 | Provider (旧) | Riverpod (新) |
|---|---|---|
| 依赖 Context | ✅ 强依赖 (BuildContext) | ❌ 无依赖 (WidgetRef 或 Ref) |
| 类型安全 | ⚠️ 运行时检查 (容易崩溃) | ✅ 编译时检查 (配合代码生成) |
| 异步支持 | 🆗 需要 FutureProvider | 🚀 原生强大 (AsyncValue, when) |
| 状态组合 | 😐 较难,容易嵌套地狱 | 🤩 极简 (ref.watch 其他 Provider) |
| 测试难度 | 😫 需要 Mock Context | 😃 极易 (直接创建 ProviderContainer) |
| 代码量 | 多 (样板代码) | 少 (配合 @riverpod 宏) |
| 学习曲线 | 低 (但精通难) | 中 (概念多,但逻辑清晰) |
-
始终使用代码生成 (
@riverpod) :不要手动编写Provider(...),让宏帮你处理类型安全和样板代码。 -
拆分小 Provider:不要试图用一个 Provider 管理所有状态。将计数器、用户信息、主题设置拆分成不同的 Provider,然后按需组合。
-
善用
AsyncValue:处理异步数据时,利用.when()方法优雅地处理loading,data,error三种状态,避免大量的if/else判断。 -
区分
watch和read:- 在
build方法中需要重建 UI时用ref.watch。 - 在事件回调(如按钮点击)中修改数据时用
ref.read(...).notifier。 - 在非 build 环境(如服务类)中用
ref.read或ref.listen。
- 在
以下是自己的理解修正:
-
watch:是订阅者。它监听 Provider 的数据变化,一旦变化,自动触发 UI 刷新(或 Provider 重算)。 -
read:是获取动作。- 在事件回调(如按钮点击)中,我们使用
ref.read(provider.notifier)来获取控制器,然后调用它的方法来修改数据。 - 修改数据后,Riverpod 会自动通知所有
watch该数据的地方进行刷新。
- 在事件回调(如按钮点击)中,我们使用
-
notifier:是控制器(遥控器)。它持有修改数据的方法(如increment)。
| 你的目的 | 应该用什么? | 结果 |
|---|---|---|
| 显示数据 (Text, Image, List) | ref.watch() | 数据变,UI 自动刷新 ✅ |
| 按钮点击/手势 (修改数据) | ref.read(...).notifier | 获取控制器,修改数据 ✅ |
| 按钮点击/手势 (读取参数) | ref.read() | 获取当前值,用于逻辑判断 ✅ |
| 定时器/异步回调 | ref.read() | 获取最新值,避免闭包旧值 ✅ |
| 纯 Dart 类/服务 | ref.read() | 访问全局状态 ✅ |
| Build 中显示数据 | ref.read() | ❌ 界面不会更新 (Bug) |