📚 Flutter 状态管理系列文章目录
-
Flutter 状态管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux)
-
setState() 使用详解:原理及注意事项
-
InheritedWidget 组件使用及原理
-
Flutter 中 Provider 的使用、注意事项与原理解析(含代码实战)
-
GetX 用法详细解析以及注意事项
-
Flutter BLoC 使用详细解析
-
Flutter MobX 响应式原理与实战详解
-
Flutter Riverpod 使用详细解析
-
Riverpod原理解析(实现一个自己的Riverpod
源码地址
📌 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 的根本限制而设计的。”
来源:https://riverpod.dev
✅ 总结对比(类比说明)
类比对象 | 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: flutterprovider: ^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: flutterflutter_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
源码地址
📚 Flutter 状态管理系列文章目录
-
Flutter 状态管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux)
-
setState() 使用详解:原理及注意事项
-
InheritedWidget 组件使用及原理
-
Flutter 中 Provider 的使用、注意事项与原理解析(含代码实战)
-
GetX 用法详细解析以及注意事项
-
Flutter BLoC 使用详细解析
-
Flutter MobX 响应式原理与实战详解
-
Flutter Riverpod 使用详细解析
-
Riverpod原理解析(实现一个自己的Riverpod