- Riverpod 概述
(1) 什么是 Riverpod
-
定义:Riverpod 是一个现代化的 Flutter 状态管理库,由 Rémi Rousselet 开发,作为 Provider 包的升级替代品。
-
核心特点:
- 无上下文依赖:不像 Provider 依赖 BuildContext,Riverpod 使用 WidgetRef 或 Ref 访问状态。
- 类型安全:编译时检查 Provider 类型,减少运行时错误。
- 模块化:支持多种 Provider 类型(如 Provider、StateNotifierProvider、AsyncNotifierProvider)。
- 可测试性:无需模拟 BuildContext,易于单元测试。
- 灵活性:支持异步状态、组合 Provider 和全局/局部作用域。
-
与 Provider 的区别:
- Provider 基于 InheritedWidget,需要 context.watch/context.read。
- Riverpod 使用 ProviderScope,通过 ref 访问状态,无需上下文。
- Riverpod 提供更丰富的 API(如 select、family),支持复杂场景。
(2) 核心概念
- Provider:声明状态的单元(如值、对象、状态机)。
- Ref:访问和操作 Provider 的接口,类似 BuildContext 的替代品。
- ProviderScope:Riverpod 的作用域容器,管理 Provider 的生命周期。
- ConsumerWidget:订阅 Provider 的 Widget,类似 Consumer。
- Notifier:管理状态的类(如 StateNotifier、AsyncNotifier)。
(3) 适用场景
- 中小型应用:简单状态管理(如主题、计数器)。
- 复杂应用:多模块、异步操作(如网络请求、实时数据)。
- 高可测试性需求:需要单元测试和 mock 数据。
- 跨平台一致性:结合 Clean Architecture,管理 UI 和业务逻辑。
- 源码分析:Riverpod 核心实现
为了深入理解 Riverpod 的工作原理,我们分析 package:riverpod 和 package:flutter_riverpod 的核心源码,聚焦 Provider、StateNotifierProvider 和 ProviderScope。
(1) Provider
-
源码位置:package:riverpod/lib/src/provider.dart
-
关键代码:
dart
class Provider<T> extends AlwaysAliveProviderBase<T, T> { Provider(this._create, {String? name}) : super(name); final Create<T, ProviderRef<T>> _create; @override T get state => _create(_ref!); } -
分析:
- Provider 是一个不可变状态的容器,通过 _create 函数生成值。
- 继承 AlwaysAliveProviderBase,表示 Provider 始终存活(除非被 override 或销毁)。
- _ref 是 ProviderRef,提供访问其他 Provider 或控制生命周期的接口。
-
用途:存储常量、依赖注入(如服务、配置)。
(2) StateNotifierProvider
-
源码位置:package:riverpod/lib/src/state_notifier.dart
-
关键代码:
dart
class StateNotifierProvider<Notifier extends StateNotifier<T>, T> extends ProviderBase<StateNotifier<T>, T> { StateNotifierProvider(this._create, {String? name}) : super(name); final Create<Notifier, Ref<StateNotifier<T>>> _create; @override T get state => _create(_ref!).state; } abstract class StateNotifier<T> extends ChangeNotifier { T _state; StateNotifier(this._state); T get state => _state; set state(T value) { if (_state != value) { _state = value; notifyListeners(); } } } -
分析:
- StateNotifierProvider 管理 StateNotifier 实例,暴露其 state。
- StateNotifier 是一个可变状态容器,使用 notifyListeners 通知 UI 更新。
- _create 初始化 Notifier,允许依赖注入。
-
用途:管理动态状态(如计数器、表单)。
(3) ProviderScope
-
源码位置:package:flutter_riverpod/lib/src/provider_scope.dart
-
关键代码:
dart
class ProviderScope extends InheritedWidget { ProviderScope({ required this.container, required Widget child, }) : super(child: child); final ProviderContainer container; @override bool updateShouldNotify(ProviderScope oldWidget) { return container != oldWidget.container; } } class ProviderContainer { final Map<ProviderBase, _ProviderState> _states = {}; T read<T>(ProviderBase<T> provider) { final state = _states.putIfAbsent(provider, () => provider.createState()); return state.value; } } -
分析:
- ProviderScope 是一个 InheritedWidget,管理 ProviderContainer。
- ProviderContainer 存储所有 Provider 的状态(_states),通过 read 访问值。
- 支持 overrideWith 重写 Provider,适合测试和局部作用域。
-
用途:定义 Provider 的生命周期和作用域。
(4) ConsumerWidget
-
源码位置:package:flutter_riverpod/lib/src/consumer.dart
-
关键代码:
dart
abstract class ConsumerWidget extends Widget { Widget build(BuildContext context, WidgetRef ref); } class Consumer extends ConsumerWidget { Consumer({required this.builder}); final Widget Function(BuildContext, WidgetRef, Widget?) builder; @override Widget build(BuildContext context, WidgetRef ref) { return builder(context, ref, null); } } -
分析:
- ConsumerWidget 通过 WidgetRef 访问 Provider,订阅状态变化。
- builder 允许局部订阅,减少重绘。
- WidgetRef 是 Ref 的子类,封装 ProviderContainer 的访问逻辑。
- Riverpod 的核心功能与使用
(1) Provider 类型
Riverpod 提供多种 Provider,满足不同场景:
-
Provider:只读值(如配置、服务)。
dart
final configProvider = Provider((ref) => AppConfig(apiUrl: 'https://api.example.com')); -
StateProvider:简单的可变状态(不推荐,建议用 StateNotifierProvider)。
dart
final counterProvider = StateProvider((ref) => 0); -
StateNotifierProvider:复杂状态管理。
dart
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter()); -
AsyncNotifierProvider:异步状态(如网络请求)。
dart
final userProvider = AsyncNotifierProvider<UserNotifier, User>(() => UserNotifier()); -
StreamProvider:流式数据(如 WebSocket)。
dart
final streamProvider = StreamProvider((ref) => Stream.periodic(Duration(seconds: 1), (i) => i)); -
FutureProvider:一次性异步操作。
dart
final dataProvider = FutureProvider((ref) => fetchData());
(2) 高级功能
-
依赖注入:
-
通过 ref.read 或 ref.watch 访问其他 Provider:
dart
final apiProvider = Provider((ref) => ApiService()); final dataProvider = FutureProvider((ref) { final api = ref.read(apiProvider); return api.fetchData(); });
-
-
Family Provider:
-
支持参数化 Provider:
dart
final userProvider = Provider.family<User, String>((ref, userId) => User(id: userId));
-
-
Select:
-
选择性监听状态,减少重绘:
dart
final count = ref.watch(counterProvider.select((state) => state.count));
-
-
AutoDispose:
-
自动销毁 Provider:
dart
final tempProvider = Provider.autoDispose((ref) => TempData());
-
-
Override:
-
测试或局部作用域重写:
dart
ProviderScope( overrides: [configProvider.overrideWithValue(AppConfig(apiUrl: 'test'))], child: MyApp(), );
-
(3) 生命周期管理
-
ProviderScope:控制 Provider 的存活范围。
-
ref.onDispose:清理资源:
dart
final timerProvider = Provider((ref) { final timer = Timer.periodic(Duration(seconds: 1), (_) {}); ref.onDispose(() => timer.cancel()); return timer; });
- 实践案例:Todo 应用(Riverpod + Clean Architecture)
我们实现一个 Todo 应用,结合 Riverpod 和 Clean Architecture,展示状态管理、异步操作和模块化设计。
(1) 项目结构
lib/
├── data/
│ └── repositories/
│ └── todo_repository.dart
├── domain/
│ ├── entities/
│ │ └── todo.dart
│ ├── repositories/
│ │ └── todo_repository.dart
│ └── usecases/
│ └── add_todo.dart
│ └── get_todos.dart
├── presentation/
│ ├── pages/
│ │ └── todo_page.dart
│ └── providers/
│ └── todo_provider.dart
├── main.dart
(2) 代码实现
Data 层
dart
// lib/data/repositories/todo_repository.dart
import '../../domain/entities/todo.dart';
import '../../domain/repositories/todo_repository.dart';
class TodoRepositoryImpl implements TodoRepository {
final List<Todo> _todos = [];
@override
Future<List<Todo>> getTodos() async {
await Future.delayed(const Duration(milliseconds: 500)); // 模拟网络
return _todos;
}
@override
Future<void> addTodo(Todo todo) async {
await Future.delayed(const Duration(milliseconds: 300));
_todos.add(todo);
}
}
Domain 层
dart
// lib/domain/entities/todo.dart
class Todo {
final String id;
final String title;
final bool completed;
Todo({required this.id, required this.title, this.completed = false});
}
// lib/domain/repositories/todo_repository.dart
import '../entities/todo.dart';
abstract class TodoRepository {
Future<List<Todo>> getTodos();
Future<void> addTodo(Todo todo);
}
// lib/domain/usecases/get_todos.dart
import '../entities/todo.dart';
import '../repositories/todo_repository.dart';
class GetTodos {
final TodoRepository repository;
GetTodos(this.repository);
Future<List<Todo>> execute() => repository.getTodos();
}
// lib/domain/usecases/add_todo.dart
import '../entities/todo.dart';
import '../repositories/todo_repository.dart';
class AddTodo {
final TodoRepository repository;
AddTodo(this.repository);
Future<void> execute(Todo todo) => repository.addTodo(todo);
}
Presentation 层
dart
// lib/presentation/providers/todo_provider.dart
import 'package:riverpod/riverpod.dart';
import '../../data/repositories/todo_repository.dart';
import '../../domain/entities/todo.dart';
import '../../domain/usecases/add_todo.dart';
import '../../domain/usecases/get_todos.dart';
final todoRepositoryProvider = Provider((ref) => TodoRepositoryImpl());
final getTodosProvider = Provider((ref) => GetTodos(ref.read(todoRepositoryProvider)));
final addTodoProvider = Provider((ref) => AddTodo(ref.read(todoRepositoryProvider)));
final todoListProvider = AsyncNotifierProvider<TodoListNotifier, List<Todo>>(TodoListNotifier.new);
class TodoListNotifier extends AsyncNotifier<List<Todo>> {
@override
Future<List<Todo>> build() async {
final getTodos = ref.read(getTodosProvider);
return getTodos.execute();
}
Future<void> addTodo(String title) async {
state = const AsyncLoading();
try {
final addTodo = ref.read(addTodoProvider);
final todo = Todo(id: DateTime.now().toString(), title: title);
await addTodo.execute(todo);
state = AsyncData([...state.value ?? [], todo]);
} catch (e, stack) {
state = AsyncError(e, stack);
}
}
}
dart
// lib/presentation/pages/todo_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/entities/todo.dart';
import '../providers/todo_provider.dart';
class TodoPage extends ConsumerWidget {
const TodoPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
return Scaffold(
appBar: AppBar(title: const Text('Todo List')),
body: todos.when(
data: (todos) => ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
trailing: Checkbox(
value: todo.completed,
onChanged: (_) {}, // 待实现
),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(child: Text('Error: $err')),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddTodoDialog(context, ref),
child: const Icon(Icons.add),
),
);
}
void _showAddTodoDialog(BuildContext context, WidgetRef ref) {
final controller = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Add Todo'),
content: TextField(controller: controller),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
ref.read(todoListProvider.notifier).addTodo(controller.text);
Navigator.pop(context);
},
child: const Text('Add'),
),
],
),
);
}
}
Main 入口
dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'presentation/pages/todo_page.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: const TodoPage(),
);
}
}
(3) 分析
-
Clean Architecture:
- Data 层:TodoRepositoryImpl 模拟网络请求。
- Domain 层:定义 Todo 实体、TodoRepository 接口和用例(GetTodos、AddTodo)。
- Presentation 层:TodoPage 处理 UI,todoListProvider 管理状态。
- 优点:模块化,易于测试和替换实现(如切换到真实 API)。
-
Riverpod:
- todoRepositoryProvider:注入存储库。
- getTodosProvider/addTodoProvider:封装用例。
- todoListProvider:使用 AsyncNotifierProvider 管理异步状态。
- ref.watch/ref.read:动态访问和操作状态。
-
性能优化:
- AsyncNotifier 优雅处理加载、成功和错误状态。
- ListView.builder 延迟构建,优化长列表性能。
(4) 验证
- 运行 flutter run --profile,添加 Todo 项,检查列表更新。
- 使用 DevTools 的 Widget Rebuilds 面板,验证仅 ListView 重建。
- 使用 Performance 面板,分析异步操作的耗时。
- 性能优化与高级用法
(1) 优化重绘
-
问题:ref.watch(todoListProvider) 监听整个列表,任何变化都触发重建。
-
优化:使用 select:
dart
final todoCount = ref.watch(todoListProvider.select((state) => state.value?.length ?? 0)); Text('Total: $todoCount'); -
效果:仅当列表长度变化时重建。
(2) 异步错误处理
-
优化:统一错误处理:
dart
class TodoListNotifier extends AsyncNotifier<List<Todo>> { @override Future<List<Todo>> build() async => ref.read(getTodosProvider).execute(); Future<void> addTodo(String title) async { state = const AsyncLoading(); state = await AsyncValue.guard(() async { final addTodo = ref.read(addTodoProvider); final todo = Todo(id: DateTime.now().toString(), title: title); await addTodo.execute(todo); return [...state.value ?? [], todo]; }); } } -
效果:AsyncValue.guard 自动捕获异常,简化错误处理。
(3) Family Provider
-
示例:按 ID 获取 Todo:
dart
final todoProvider = Provider.family<Todo?, String>((ref, id) { final todos = ref.watch(todoListProvider).value ?? []; return todos.firstWhere((todo) => todo.id == id, orElse: () => null); }); class TodoDetail extends ConsumerWidget { final String todoId; const TodoDetail(this.todoId, {super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final todo = ref.watch(todoProvider(todoId)); return Scaffold( appBar: AppBar(title: Text(todo?.title ?? 'Not Found')), body: Text(todo?.title ?? ''), ); } } -
效果:动态生成 Provider,适合细节页面。
(4) 测试支持
-
单元测试:
dart
import 'package:flutter_test/flutter_test.dart'; import 'package:riverpod/riverpod.dart'; import '../data/repositories/todo_repository.dart'; import '../domain/usecases/add_todo.dart'; import '../domain/entities/todo.dart'; void main() { test('AddTodo adds todo to repository', () async { final container = ProviderContainer(); final repository = TodoRepositoryImpl(); final addTodo = AddTodo(repository); await addTodo.execute(Todo(id: '1', title: 'Test')); expect((await repository.getTodos()).length, 1); }); } -
Widget 测试:
dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import '../presentation/pages/todo_page.dart'; void main() { testWidgets('TodoPage adds todo', (tester) async { await tester.pumpWidget( const ProviderScope(child: MaterialApp(home: TodoPage())), ); await tester.tap(find.byIcon(Icons.add)); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField), 'Test Todo'); await tester.tap(find.text('Add')); await tester.pumpAndSettle(); expect(find.text('Test Todo'), findsOneWidget); }); } -
效果:Riverpod 的无上下文依赖简化测试,无需模拟 BuildContext。
- 总结
-
核心特点:
- 无上下文依赖、类型安全、高可测试性。
- 支持多种 Provider(Provider、StateNotifierProvider、AsyncNotifierProvider)。
-
源码洞察:
- ProviderScope 和 ProviderContainer 管理状态生命周期。
- StateNotifier 和 AsyncNotifier 提供灵活的状态更新。
-
高级用法:
- select 优化重绘,family 支持参数化,override 便于测试。
- AsyncValue.guard 简化异步错误处理。
-
适用场景:
- 复杂应用、异步操作、高可测试性需求。
- 结合 Clean Architecture,构建可扩展项目。