Riverpod:编译时安全的状态管理
hardflutterriverpodproviderrefcodegenconsumer-widgetflutter-hooks
为什么需要 Riverpod?
Riverpod 是 Provider 的作者(Remi Rousselet)重新设计的状态管理库,解决了 Provider 的几个痛点:
- 编译时安全:Provider 的类型错误在运行时才报,Riverpod 在编译时暴露
- 不依赖 BuildContext:可以在 Widget 外部访问状态
- 异步状态一等公民:内置
AsyncValue,异步加载/错误/数据 三态自动处理 - 可组合:Provider 之间可以互相依赖,且类型安全
flutter pub add flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator
Provider 类型
Riverpod 有多种 Provider,按用途选择:
| Provider | 用途 | 是否可变 |
|---|---|---|
Provider |
不可变值、服务、Repository | ❌ |
StateProvider |
简单可变值(如 bool、int) | ✅ |
StateNotifierProvider |
复杂可变状态(旧写法) | ✅ |
NotifierProvider |
复杂可变状态(推荐新写法) | ✅ |
FutureProvider |
异步单次请求 | ❌ |
StreamProvider |
实时数据流 | ❌ |
AsyncNotifierProvider |
异步+可变状态 | ✅ |
基础用法(不使用代码生成)
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 定义 Provider
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
class Counter extends Notifier<int> {
@override
int build() => 0; // 初始值
void increment() => state++;
void decrement() => state--;
}
// 2. 在应用根部包裹 ProviderScope(替代 BuildContext 的注册中心)
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
// 3. 在 Widget 中消费(继承 ConsumerWidget)
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // 订阅
return Scaffold(
body: Text('$count'),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
代码生成写法(推荐)
代码生成写法大幅减少样板,且支持参数化 Provider(Family):
// counter_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
// 生成:counterProvider
# 生成代码
dart run build_runner watch
异步状态:FutureProvider
@riverpod
Future<List<User>> userList(UserListRef ref) async {
final api = ref.watch(apiClientProvider);
return api.fetchUsers();
}
// 消费
class UserListPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncUsers = ref.watch(userListProvider);
return asyncUsers.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (_, i) => ListTile(title: Text(users[i].name)),
),
);
}
}
AsyncValue 的 when 方法强制处理三种状态,避免遗漏。
Provider 依赖(ref.watch 的威力)
// repository 依赖 apiClient
@riverpod
UserRepository userRepository(UserRepositoryRef ref) {
final api = ref.watch(apiClientProvider); // 类型安全的依赖
return UserRepository(api);
}
// 如果 apiClientProvider 更新,userRepository 自动重新创建
Family:带参数的 Provider
@riverpod
Future<User> user(UserRef ref, {required int userId}) async {
return ref.watch(userRepositoryProvider).fetchUser(userId);
}
// 用法
ref.watch(userProvider(userId: 42))
ref 的主要方法
| 方法 | 说明 | 用于 |
|---|---|---|
ref.watch(provider) |
订阅,变化时 rebuild | build 方法 |
ref.read(provider) |
读取一次,不订阅 | 事件处理器 |
ref.listen(provider, cb) |
监听变化,执行回调 | Side effects |
ref.invalidate(provider) |
使 Provider 失效(重新创建) | 刷新 |
Riverpod 与 Provider 对比
| Provider | Riverpod | |
|---|---|---|
| 编译时安全 | ❌ | ✅ |
| 依赖 context | ✅ | ❌ |
| 异步支持 | 手动 | AsyncValue 内置 |
| Provider 覆写 | 困难 | ProviderScope 覆写,测试方便 |
| 学习曲线 | 低 | 中 |