📚 Flutter 状态管理系列文章目录
📌 Riverpod 是 Flutter 官方推荐的状态管理方案之一,相比 Provider 更灵活、安全、支持组合、异步、热重载、IDE 类型推导。
📁 1. 安装依赖
在 pubspec.yaml
添加:
dependencies:
flutter_riverpod: ^2.5.1
然后执行:
flutter pub get
🧠 2. 核心概念简述
类型 | 描述 | 示例用途 |
---|---|---|
Provider |
只读数据 | 常量、服务类 |
StateProvider |
原始值的可变状态(int/bool) | 计数器、开关 |
StateNotifierProvider |
复杂对象状态 + 控制逻辑 | 列表、对象、业务逻辑 |
ChangeNotifierProvider |
ChangeNotifier 封装的旧模式 | 第三方 SDK 封装 |
FutureProvider |
异步加载 | 网络请求、初始化数据 |
StreamProvider |
实时数据流 | WebSocket、定时器 |
🧪 3. 实战代码讲解
示例场景:Todo 应用
✅ Provider
:静态或只读依赖
final greetingProvider = Provider<String>((ref) {
return "Hello, Riverpod!";
});
class GreetingWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider);
return Text(greeting);
}
}
💡 用于配置、单例、不可变依赖。
✅ StateProvider
:简单状态(如 int、bool)
final counterProvider = StateProvider<int>((ref) => 0);
class CounterPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('Increment'),
),
],
);
}
}
💡 简单值状态管理的首选。
✅ StateNotifierProvider
:结构化业务逻辑
定义模型:
class Todo {
final String id;
final String title;
Todo({required this.id, required this.title});
}
定义状态控制器:
class TodoNotifier extends StateNotifier<List<Todo>> {
TodoNotifier() : super([]);
void add(String title) {
final todo = Todo(id: DateTime.now().toIso8601String(), title: title);
state = [...state, todo];
}
void remove(String id) {
state = state.where((t) => t.id != id).toList();
}
}
final todoListProvider =
StateNotifierProvider<TodoNotifier, List<Todo>>((ref) => TodoNotifier());
页面使用:
class TodoListView extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
return ListView(
children: todos
.map((t) => ListTile(
title: Text(t.title),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => ref.read(todoListProvider.notifier).remove(t.id),
),
))
.toList(),
);
}
}
💡 面向对象式的推荐写法,利于维护与测试。
✅ ChangeNotifierProvider
:传统类兼容封装
class AuthModel extends ChangeNotifier {
String _email = '';
String get email => _email;
void login(String email) {
_email = email;
notifyListeners();
}
}
final authProvider = ChangeNotifierProvider((ref) => AuthModel());
使用:
class AuthView extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final auth = ref.watch(authProvider);
return Column(
children: [
Text('Email: ${auth.email}'),
ElevatedButton(
onPressed: () => ref.read(authProvider).login('user@example.com'),
child: Text('Login'),
),
],
);
}
}
⚠️ 尽量用于兼容旧代码或外部库。
✅ FutureProvider
:异步加载数据
final userInfoProvider = FutureProvider<String>((ref) async {
await Future.delayed(Duration(seconds: 1));
return "Async User Loaded";
});
使用:
class UserInfoWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final userInfo = ref.watch(userInfoProvider);
return userInfo.when(
data: (name) => Text('User: $name'),
loading: () => CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
);
}
}
💡 支持 loading/error/data 三种状态,十分方便。
✅ StreamProvider
:实时流监听
final tickProvider = StreamProvider<int>((ref) async* {
int count = 0;
while (true) {
await Future.delayed(Duration(seconds: 1));
yield ++count;
}
});
使用:
class TimerWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final tick = ref.watch(tickProvider);
return tick.when(
data: (val) => Text('Tick: $val'),
loading: () => Text('Waiting...'),
error: (e, _) => Text('Error: $e'),
);
}
}
💡 非常适合实现 WebSocket、倒计时、后台上传监听等。
🧬 4 Riverpod Provider 的生命周期详解
Riverpod 的每个 Provider
都有清晰的生命周期管理机制,这是它相比 Provider
(老版)或 setState
更加安全、强大的关键优势之一。
生命周期由 Riverpod 自动管理,确保资源释放、缓存优化、避免内存泄漏。
🎯 生命周期触发时机概览
生命周期阶段 | 描述 | 对应钩子函数 / 方法 |
---|---|---|
创建 | 第一次被 ref.watch() / ref.read() 使用 |
provider 的构造函数 |
监听中 | 有 widget 或 provider 依赖它 | ref.watch() 触发监听 |
取消监听 | 最后一个监听者移除后,开始进入 dispose 倒计时 | keepAlive 决定回收策略 |
销毁 | 无依赖 + 被标记为回收 | onDispose 触发 |
📌 1. Provider 的创建与首次使用
- 只有在首次使用(如
ref.watch()
)时才真正创建。 - Provider 是懒加载的(Lazy Initialized)。
final timestampProvider = Provider<DateTime>((ref) {
print('✅ 创建时间: ${DateTime.now()}');
return DateTime.now();
});
📌 2. 自动销毁与缓存策略
🔁 默认行为:自动销毁(AutoDispose)
- 若无任何
ref.watch()
使用该 Provider,它会被自动销毁。 - 优化内存和性能,避免长时间驻留。
final myProvider = Provider.autoDispose<int>((ref) {
print('🌀 创建');
ref.onDispose(() => print('❌ 销毁'));
return 42;
});
使用中断后会触发销毁:
// 页面离开,或 ref.invalidate()
📌 3. 保持活跃 ref.keepAlive()
final counterProvider = Provider.autoDispose<int>((ref) {
final link = ref.keepAlive(); // 👈 阻止销毁
Future.delayed(Duration(seconds: 30), () {
print('🚨 30 秒后允许销毁');
link.close(); // 👈 恢复可销毁
});
ref.onDispose(() {
print('⛔ 被销毁');
});
return 123;
});
✅ 用于防止高频初始化,如网络缓存、状态记录。
📌 4. ref.onDispose()
所有 Provider 都可使用:
ref.onDispose(() {
print('🧹 清理资源...');
});
常用于:
- 关闭连接(如 WebSocket、Stream、Timer)
- 释放资源(如 Controller、Database)
- 终止订阅
✅ 总结:各 Provider 生命周期对比
Provider 类型 | 是否可自动销毁 | 是否可 keepAlive | 是否有 onDispose |
---|---|---|---|
Provider |
❌ 否(持久) | ✅ 支持 | ✅ 支持 |
Provider.autoDispose |
✅ 是 | ✅ 支持 | ✅ 支持 |
StateProvider |
❌ 否 | ✅ 支持 | ✅ 支持 |
StateNotifierProvider |
❌ 否(除非手动) | ✅ 支持 | ✅ 支持 |
FutureProvider.autoDispose |
✅ 默认 | ✅ 支持 | ✅ 支持 |
StreamProvider.autoDispose |
✅ 默认 | ✅ 支持 | ✅ 支持 |
🛠 应用实战建议
场景 | 建议 Provider 类型 |
---|---|
临时页面数据(如表单页) | .autoDispose 类型 |
全局共享状态(如登录信息) | 普通 Provider / StateNotifierProvider |
长连接、Timer、Stream | ref.onDispose + .keepAlive() |
一次性请求缓存(配置、Token) | 使用 keepAlive() 手动控制回收 |
✅ 5. 多 Provider 合并管理
final appProviders = ProviderContainer(overrides: []);
或使用 ConsumerStatefulWidget
在生命周期中集中监听多个状态。
🧱 6. Riverpod 与传统Provider 的关系
✅ 简明回答:
Riverpod 并不是基于传统 Provider 的封装,它是 完全重写的一个全新状态管理框架,由同一个作者 Remi Rousselet 开发,但底层逻辑、API 设计、运行机制都不同。
🔍 深度解析:Riverpod 与 Provider 的关系
项目 | Provider | Riverpod |
---|---|---|
作者 | Remi Rousselet | Remi Rousselet |
上线时间 | 2018 | 2020 |
设计理念 | 面向 widget tree 的 InheritedWidget | 独立于 widget tree,可纯 Dart 运行 |
架构关系 | Flutter Widget 的封装 | 重新设计的响应式依赖系统(非封装) |
生命周期 | 依赖 Widget 生命周期 | 由容器独立管理,更安全可控 |
热重载支持 | 差 | 完整支持热重载(尤其适合大型项目) |
Test 测试 | 需依赖 Widget 或 MockContext | 完全脱离 UI,可单测 provider |
🧠 核心区别举例
✅ Provider 示例
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
);
- 必须包在 widget tree 里,依赖
BuildContext
。 - 很多依赖关系写在 UI 组件中,不利于拆分测试。
✅ Riverpod 示例
final counterProvider = StateProvider((ref) => 0);
- 不依赖 WidgetTree,任何地方都可以
ref.watch
。 - 甚至可以在纯 Dart 类、后台逻辑中使用,适合模块化架构。
📦 为什么作者重写 Riverpod?
作者自己在 Riverpod 官网解释了:
“Provider 的设计绑定在 widget tree 上,不足以应对复杂、灵活的状态管理需求;Riverpod 是为了解决 Provider 的根本限制而设计的。”
✅ 总结对比(类比说明)
类比对象 | Provider | Riverpod |
---|---|---|
类比 Flutter 状态 | setState() |
Bloc / Redux / MobX |
类比 HTML 框架 | jQuery | React + Hooks |
是否独立可测 | ❌ 需要 widget tree | ✅ 完全支持纯 Dart 单测 |
是否推荐未来使用 | 🟡 小项目可继续用 | ✅ 官方推荐未来项目默认使用 Riverpod |
7. 🔄 Provider 到 Riverpod 迁移指南
✅ 1. 概念映射表
Provider 中的概念 | Riverpod 对应概念 | 用途 |
---|---|---|
ChangeNotifierProvider |
ChangeNotifierProvider (兼容) |
保持一致,用于 SDK 封装 |
Provider<T> |
Provider<T> |
只读依赖 |
StateProvider<T> (无) |
✅ Riverpod 独有,推荐替代 setState |
管理基本类型如 int、bool |
ConsumerWidget |
ConsumerWidget |
响应式 UI |
context.read() |
ref.read() |
读取值,不触发 rebuild |
context.watch() |
ref.watch() |
读取并监听 |
MultiProvider |
❌ 不需要,Riverpod 使用全局注册 Provider | 提升模块化与性能 |
依赖于 BuildContext |
✅ 不依赖 BuildContext | 更适合逻辑复用、单元测试 |
⚠️ 2. Provider 示例 VS Riverpod 对比代码
旧:使用 Provider(ChangeNotifier)
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 注册
ChangeNotifierProvider(create: (_) => CounterModel())
// 使用
final counter = Provider.of<CounterModel>(context);
新:使用 Riverpod(StateNotifier)
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
}
// UI 使用
class CounterWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
🔁 3. 推荐迁移步骤
步骤 | 内容 |
---|---|
✅ 第一步 | 安装 flutter_riverpod 并用 ProviderScope 包裹 MaterialApp |
✅ 第二步 | 从功能简单的模块开始迁移(如计数器页面) |
✅ 第三步 | 将 ChangeNotifier 转为 StateNotifier / Notifier |
✅ 第四步 | 将 context.read/watch 替换为 ref.read/watch |
✅ 第五步 | 清除旧 Provider 引入,完全替换后删除 Provider |
🤝 4. Riverpod 和 Provider 可以共存吗?
✅ 可以! 它们不会互相干扰,可用于渐进式迁移。
void main() {
runApp(
MultiProvider(
providers: [ChangeNotifierProvider(create: (_) => OldProvider())],
child: ProviderScope(
child: MyApp(),
),
),
);
}
📌 注意顺序,
ProviderScope
是 Riverpod 的入口容器,MultiProvider
是旧 Provider 的入口。
🧪 5. 测试更简单
Riverpod 的 Provider 不依赖 Widget Tree,可直接测试:
void main() {
test('Counter increments', () {
final container = ProviderContainer();
final notifier = container.read(counterProvider.notifier);
notifier.increment();
expect(container.read(counterProvider), 1);
});
}
🧭 6. 补充推荐迁移写法
✅ 将 Provider 包装类抽离为方法:
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return _buildCountText(count);
}
Widget _buildCountText(int count) => Text('$count');
✅ 避免业务逻辑写在 build 方法中,便于测试和维护。
📚 工具推荐
工具 | 用途 |
---|---|
riverpod_lint |
Riverpod 专用 Lint |
riverpod_generator |
自动生成 Notifier 等模板 |
state_notifier |
搭配使用,可手动控制状态 |
freezed |
状态模型不可变 + 模式匹配 |
✅ 小结:迁移推荐策略
- 🧱 小项目:直接重构成 Riverpod(更清晰)
- 🧬 中大型项目:新模块优先使用 Riverpod,旧模块稳定后再迁移
- 🧪 单元测试驱动:逻辑类迁移优先(先把模型和 service 拆出来)
- 🔁 可并存:逐步过渡,保持主 app 稳定
下面是一个完整的 计数器 Counter 示例,包括:
- ✅ Provider 旧写法(使用
ChangeNotifier
) - ✅ Riverpod 新写法(使用
StateNotifier
)
每一套代码都包含完整的 main.dart
、model
和 UI
,你可以直接运行进行对比。
✅ 迁移前(使用 Provider)
📄 pubspec.yaml 添加依赖
dependencies:
flutter:
sdk: flutter
provider: ^6.1.0
📁 counter_model.dart
import 'package:flutter/foundation.dart';
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
📄 main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';
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) {
final counter = Provider.of<CounterModel>(context);
return Scaffold(
appBar: AppBar(title: Text('Provider Counter')),
body: Center(child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24))),
floatingActionButton: FloatingActionButton(
onPressed: counter.increment,
child: Icon(Icons.add),
),
);
}
}
✅ 迁移后(使用 Riverpod)
📄 pubspec.yaml 添加依赖
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
📁 counter_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider 和逻辑
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void reset() => state = 0;
}
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
(ref) => CounterNotifier(),
);
// 🚀 抽离逻辑方法(对外暴露 API)
int useCounter(WidgetRef ref) => ref.watch(counterProvider);
void incrementCounter(WidgetRef ref) => ref.read(counterProvider.notifier).increment();
void resetCounter(WidgetRef ref) => ref.read(counterProvider.notifier).reset();
📄 main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_controller.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(home: CounterPage());
}
}
class CounterPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final count = useCounter(ref); // 👈 抽离的读取方法
return Scaffold(
appBar: AppBar(title: Text('抽离逻辑示例')),
body: Center(child: Text('Count: $count', style: TextStyle(fontSize: 24))),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.small(
heroTag: 'add',
onPressed: () => incrementCounter(ref), // 👈 抽离的操作方法
child: Icon(Icons.add),
),
const SizedBox(width: 10),
FloatingActionButton.small(
heroTag: 'reset',
onPressed: () => resetCounter(ref), // 👈 抽离 reset 方法
child: Icon(Icons.refresh),
),
],
),
);
}
}
🧭 对比总结
项目 | Provider | Riverpod |
---|---|---|
状态类 | ChangeNotifier |
StateNotifier<int> |
状态读取 | Provider.of() |
ref.watch() |
状态变更 | notifyListeners() |
state = ... |
UI绑定 | StatelessWidget + Provider |
ConsumerWidget |
生命周期控制 | 依赖 Widget Tree | 独立容器控制(ProviderScope) |
🧭 8. 最佳实践建议
建议 | 理由或说明 |
---|---|
使用 StateNotifier 替代 ChangeNotifier |
更轻量、更明确、更好测试 |
避免直接 ref.read().state++ |
推荐封装方法防止状态逻辑混乱 |
拆分 Provider 文件 | 提高可读性和可维护性 |
将 ref.read() 写到方法中 |
避免在 UI 中写业务逻辑 |
📦 附加资源推荐
- 官方文档:https://riverpod.dev
- IDE 插件:
Riverpod Snippets
- 推荐组合:Riverpod + go_router + freezed + dio