简介
Riverpod 是 Provider 的重新设计版本,它提供了更安全的依赖管理和更好的代码组织方式。Riverpod 完全支持编译时安全,并且可以轻松处理异步数据。
基本概念
Provider
// 基础 Provider
final nameProvider = Provider<String>((ref) => 'John Doe');
// 状态 Provider
final counterProvider = StateProvider<int>((ref) => 0);
// 异步 Provider
final userProvider = FutureProvider<User>((ref) async {
final repository = ref.read(repositoryProvider);
return repository.fetchUser();
});
StateNotifier
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() => state++;
void decrement() => state--;
}
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
return Counter();
});
基本用法
提供状态
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
使用状态
// 读取状态
Consumer(
builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
},
);
// 修改状态
ref.read(counterProvider.notifier).increment();
完整示例
// 用户模型
class User {
final String id;
final String name;
final int age;
User({
required this.id,
required this.name,
required this.age,
});
}
// 用户状态
class UserState {
final User? user;
final bool isLoading;
final String? error;
UserState({
this.user,
this.isLoading = false,
this.error,
});
UserState copyWith({
User? user,
bool? isLoading,
String? error,
}) {
return UserState(
user: user ?? this.user,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
);
}
}
// 用户 Notifier
class UserNotifier extends StateNotifier<UserState> {
final UserRepository _repository;
UserNotifier(this._repository) : super(UserState());
Future<void> fetchUser() async {
state = state.copyWith(isLoading: true, error: null);
try {
final user = await _repository.fetchUser();
state = state.copyWith(
user: user,
isLoading: false,
);
} catch (e) {
state = state.copyWith(
error: e.toString(),
isLoading: false,
);
}
}
Future<void> updateUser(String name, int age) async {
state = state.copyWith(isLoading: true, error: null);
try {
final updatedUser = await _repository.updateUser(
state.user!.id,
name,
age,
);
state = state.copyWith(
user: updatedUser,
isLoading: false,
);
} catch (e) {
state = state.copyWith(
error: e.toString(),
isLoading: false,
);
}
}
}
// Provider 定义
final userRepositoryProvider = Provider<UserRepository>((ref) {
return UserRepository();
});
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
final repository = ref.watch(userRepositoryProvider);
return UserNotifier(repository);
});
// 页面组件
class UserPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userProvider);
return Scaffold(
appBar: AppBar(title: Text('Riverpod Demo')),
body: Center(
child: userState.isLoading
? CircularProgressIndicator()
: userState.error != null
? Text('Error: ${userState.error}')
: userState.user == null
? ElevatedButton(
onPressed: () {
ref.read(userProvider.notifier).fetchUser();
},
child: Text('Load User'),
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Name: ${userState.user!.name}',
style: TextStyle(fontSize: 24),
),
Text(
'Age: ${userState.user!.age}',
style: TextStyle(fontSize: 24),
),
ElevatedButton(
onPressed: () {
ref.read(userProvider.notifier).updateUser(
'John Doe',
30,
);
},
child: Text('Update User'),
),
],
),
),
);
}
}
高级用法
组合 Provider
final userSettingsProvider = Provider<Settings>((ref) {
final user = ref.watch(userProvider);
return Settings(userId: user.id);
});
自动释放
final cacheProvider = Provider.autoDispose<Cache>((ref) {
final cache = Cache();
ref.onDispose(() => cache.dispose());
return cache;
});
状态保持
final counterProvider = StateProvider.autoDispose<int>((ref) {
ref.maintainState = true;
return 0;
});
最佳实践
- 页面级 provider 加 autoDispose:
FutureProvider.autoDispose((ref) => fetchDetail(id))——页面 pop 后自动释放;不加的话,每个进过的详情页数据都常驻内存(和 GetX 篇”退出登录 controller 没清”是同类坑) - 用 family 传参:
detailProvider(id)按 id 区分实例;注意 family 的参数必须不可变且==稳定——传可变对象会导致缓存 miss,每次都重复请求 - build 里 ref.watch、回调里 ref.read:和 Provider 的 watch/read 同理,watch 放错地方(比如 onPressed 里)会抛异常
- 测试用 override:
ProviderContainer(overrides: [apiProvider.overrideWithValue(mock)])——这是 Riverpod 比 GetX 强的地方,单测不用起整个 App
注意事项
- 别在 provider 里写出循环依赖:A watch B、B watch A,运行时直接抛 CircularDependencyError,编译器可不会提前告诉你
- 状态必须是不可变的:
state = [...state, item]而不是state.add(item)——直接改原对象,监听者收不到通知(和 GetX 的list.add同一个坑) - 全局用户态别 autoDispose:每次 watch 重建会闪一下 loading;常驻的状态用
ref.keepAlive()或干脆不用 autoDispose - family 别滥用:userId+tab+filter 三个 family 嵌套,参数组合爆炸;考虑把参数包成一个不可变对象,或退回普通 provider 内部 switch
实战案例
Riverpod 我研究过,没在生产项目用。它的 ref.watch 编译期安全、测试友好,设计上比 Provider 先进。但我的五个 App 已经是 GetX 技术栈(get: ^4.7.2 锁死),切 Riverpod 意味着重写所有 controller/view,收益撑不起成本。而且 Riverpod 的”正确”用法(family、autoDispose 的各种组合)学习曲线并不低,网上的最佳实践还在演进中。我的判断是:新项目可以考虑 Riverpod,老项目别折腾。技术选型里”迁移成本”是经常被低估的一项,重写一时爽,联调火葬场。
总结
Riverpod 提供了一种类型安全、可测试的状态管理方案。通过合理使用 Provider 和 StateNotifier,可以构建出可维护性更好的应用程序。理解并掌握 Riverpod 的使用对于开发高质量的 Flutter 应用至关重要。
