flutter_riverpod
: ^2.6.1 应用笔记 (一)
本文浅谈自己的关于新版Riverpod
注解生成代码的应用流程:
1. 配置
下边是一些关于注解生成代码的相关配置pubspec.yaml
:
yaml
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
flutter_riverpod: ^2.6.1 # Riverpod的核心包
riverpod_annotation: ^2.6.1 # 包含注解(如`@riverpod`)的包
freezed_annotation: ^3.1.0 # 包含一些注解(例如`@freezed`)
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.5.4 # 用于运行`riverpod_generator`,
# 读取带有`@riverpod`注解的代码,并生成相应的provider代码。
riverpod_generator: ^2.6.5 # Riverpod的代码生成器
freezed: ^3.1.0 # 代码生成器
2. 理解
要是先不用类内部属性值的比较(不同于简单的 ==),就先不用freezed_annotation
和freezed
两个插件。因为Riverpod
内部会通过 == 来比较state
值的变化,从而更新UI
;而普通自定义类默认 == 比较的是引用地址。
所以,状态类型是 简单类型(int、double、String、bool、List/Map
等不可变集合或者类内部没有比较需求,只关心对象引用,就不需要用这两个插件。freezed
的主要作用是生成不可变对象和内容比较逻辑,解决复杂状态的深度比较问题。
接下来是我对flutter_riverpod
注解生成代码的一些实践以及实践中的理解:不同于GetX
pub.dev/packages/ge... 全家桶,网上有很多对其的实战教程。flutter_riverpod
的学习耗费了太多精力了,官方文档看的一知半解,项目实战又不是最新的,教学视频寥寥无几。最后在尽力了解官方文档以及杰哥的笔记再加上自己的尝试,最重要还是尝试。简单记录一下我的理解。
2.1 函数注解生成代码
注解生成代码我把其分为两大类:函数注解生成代码和类注解生成代码。我来一一列举就可以看出他们的区别:
首先要知道的是注解生成的Provider
类型是基于函数的返回值类型得出的。
dart
@riverpod // 1
String helloWorld(Ref ref) {
return 'Hello World';
}
@Riverpod() // 2
String helloWorldCeche(Ref ref) {
return 'Hello World';
}
@Riverpod(keepAlive: true) // 3
String helloWorldCecheTwo(Ref ref) {
return 'Hello World';
}
1和2是一样的生成的都是:AutoDisposeProvider
在没有任何监听者(watch/read
)后,会自动释放资源并销毁内部状态的 Provider
。3生成的是普通 Provider
不会自己销毁。所以可以看出@riverpod
与@Riverpod()
区别就是创建的Provider
数据提供者是否能够自动销毁。
dart
@ProviderFor(helloWorld) // 1
final helloWorldProvider = AutoDisposeProvider<String>.internal(
helloWorld,
name: r'helloWorldProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$helloWorldHash,
dependencies: null,
allTransitiveDependencies: null,
);
@ProviderFor(helloWorldCeche) // 2
final helloWorldCecheProvider = AutoDisposeProvider<String>.internal(
helloWorldCeche,
name: r'helloWorldCecheProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$helloWorldCecheHash,
dependencies: null,
allTransitiveDependencies: null,
);
@ProviderFor(helloWorldCecheTwo) // 3
final helloWorldCecheTwoProvider = Provider<String>.internal(
helloWorldCecheTwo,
name: r'helloWorldCecheTwoProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$helloWorldCecheTwoHash,
dependencies: null,
allTransitiveDependencies: null,
);
当然调用他们都是用:helloWorld
是返回值,他们的state
不能够改变。这是因为函数型 Provider
(不管返回 String、Future、Stream
)是 纯计算型 Provider
,它本身没有 state
属性,只有 build
逻辑。
如果想要有可变 state
,要用类式 Provider
(Notifier
或 AsyncNotifier
)。
dart
final helloWorld = ref.watch(helloWorldProvider);
旧的用法,状态的改变很分散
dart
final myProvider = StateProvider((ref)=>100);
int myValue = ref.watch(myprovider);
ref.read(myProvider.state).state++;
接下来看几种其他常见的返回值类型分别对应哪几种Provider
dart
@riverpod
Future<int> fetchCount(Ref ref) async {
return 42;
}
@riverpod
Stream<double> numberStream(Ref ref) async* {
yield 3.14;
}
@ProviderFor(fetchCount)
final fetchCountProvider = AutoDisposeFutureProvider<int>.internal(
fetchCount,
name: r'fetchCountProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$fetchCountHash,
dependencies: null,
allTransitiveDependencies: null,
);
@ProviderFor(numberStream)
final numberStreamProvider = AutoDisposeStreamProvider<double>.internal(
numberStream,
name: r'numberStreamProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$numberStreamHash,
dependencies: null,
allTransitiveDependencies: null,
);
用法
dart
final futureCeche = ref.watch(fetchCountProvider);
futureCeche.when(data: (data) => ,error: (error, stackTrace) => ,loading: () => ,);
另外我们知道函数除了有返回值还有参数,带参数的函数注解生成代码:
dart
@riverpod // 1
String fanGreet(Ref ref, String fanName) {
return 'Hello $fanName';
}
@Riverpod(keepAlive: true) // 2
String fanGreetCeche(Ref ref, String fanName) {
return 'Hello $fanName';
}
// 1
@ProviderFor(fanGreet)
const fanGreetProvider = FanGreetFamily();
class FanGreetProvider extends AutoDisposeProvider<String> {}
// 2
@ProviderFor(fanGreetCeche)
const fanGreetCecheProvider = FanGreetCecheFamily();
class FanGreetCecheProvider extends Provider<String> {}
本质上继承的还是不同的Provider
要是想通过传入参数更改状态值的话,本质上是又创建了一个新的provider
实例,并监听新的实例。
以上是函数注解生成代码的简单示例,主要作用还是返回值,要是就这么一点作用的话,体现不出来状态管理啊,状态都没办法改变,只能在初期获取值。
2.2 类注解生成代码
直接展示例子,@riverpod
和@Riverpod()
的区别还是跟上边一样有没有AutoDispose
只不过现在生成的是NotifierProvider
2.2.1 普通
dart
@Riverpod() // 1
class FanCecheOne extends _$FanCecheOne {
@override
int build() {
return 30;
}
}
@Riverpod() // 2
class FanCecheTwo extends _$FanCecheTwo {
@override
String build() {
return 'FanCecheTwo';
}
}
@riverpod // 3
class FanCecheThree extends _$FanCecheThree {
@override
({String name, int age}) build() {
return (name: "xiaoli", age: 20);
}
}
dart
@ProviderFor(FanCecheOne) // 1
final fanCecheOneProvider =
AutoDisposeNotifierProvider<FanCecheOne, int>.internal(
FanCecheOne.new,
name: r'fanCecheOneProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$fanCecheOneHash,
dependencies: null,
allTransitiveDependencies: null,
);
@ProviderFor(FanCecheTwo) // 2
final fanCecheTwoProvider =
AutoDisposeNotifierProvider<FanCecheTwo, String>.internal(
FanCecheTwo.new,
name: r'fanCecheTwoProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$fanCecheTwoHash,
dependencies: null,
allTransitiveDependencies: null,
);
@ProviderFor(FanCecheThree) // 3
final fanCecheThreeProvider =
AutoDisposeNotifierProvider<
FanCecheThree,
({String name, int age})
>.internal(
FanCecheThree.new,
name: r'fanCecheThreeProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$fanCecheThreeHash,
dependencies: null,
allTransitiveDependencies: null,
);
返回的都是普通的数据类型,所以生成的都是NotifierProvider
2.2.2 特殊
特殊类型有Stream、Future
dart
@riverpod // 1
class UserData extends _$UserData {
@override
Future<String> build() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
return 'User Name';
}
// 可定义方法触发状态刷新
Future<void> refresh() async {
state = AsyncValue.loading(); // 手动设置为 loading
final data = await build();
state = AsyncValue.data(data);
}
}
@riverpod // 2
class CounterStream extends _$CounterStream {
@override
Stream<int> build() async* {
int i = 0;
while (true) {
await Future.delayed(Duration(seconds: 1));
yield i++;
}
}
// 可在内部定义方法,比如重置计数
void reset() {
// 需要调用 invalidate 或重新生成 Stream
ref.invalidateSelf();
}
}
生成
dart
@ProviderFor(UserData) // 1
final userDataProvider =
AutoDisposeAsyncNotifierProvider<UserData, String>.internal(
UserData.new,
name: r'userDataProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$userDataHash,
dependencies: null,
allTransitiveDependencies: null,
);
@ProviderFor(CounterStream) // 2
final counterStreamProvider =
AutoDisposeStreamNotifierProvider<CounterStream, int>.internal(
CounterStream.new,
name: r'counterStreamProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$counterStreamHash,
dependencies: null,
allTransitiveDependencies: null,
);
可以看出Future
生成的是AsyncNotifierProvider
,Stream生成的是StreamNotifierProvider
另外在构造函数中传入参数的话就可以实现Family
例子:
dart
@riverpod // 1
class UserProfile extends _$UserProfile {
@override
Future<String> build({required int userId}) async {
// 模拟根据参数请求数据
await Future.delayed(Duration(seconds: 1));
return 'User $userId';
}
}
@riverpod // 2
class TimerStream extends _$TimerStream {
@override
Stream<int> build({required int start}) async* {
int i = start;
while (true) {
await Future.delayed(Duration(seconds: 1));
yield i++;
}
}
}
生成
dart
@ProviderFor(UserProfile) // 1
const userProfileProvider = UserProfileFamily();
class UserProfileProvider
extends AutoDisposeAsyncNotifierProviderImpl<UserProfile, String> {}
@ProviderFor(TimerStream) // 2
const timerStreamProvider = TimerStreamFamily();
class TimerStreamProvider
extends AutoDisposeStreamNotifierProviderImpl<TimerStream, int> {}
本质上还是NotifierProvider
调用的列子
dart
// 验证码倒计时的状态管理
// 验证码状态
enum CaptchaState {
init, // 初始状态
counting, // 倒计时中
restart, // 可重新发送
}
@Riverpod(keepAlive: true)
class Captcha extends _$Captcha {
Timer? _timer;
@override
({CaptchaState state, int countdown}) build(String pageId) {
// 只保留定时器取消逻辑,不重置状态
ref.onDispose(() => _timer?.cancel());
return (state: CaptchaState.init, countdown: 0);
}
// 初始化倒计时
void initCaptcha() {
// 取消之前的定时器,防止它继续修改状态
_timer?.cancel();
_timer = null;
// 重置状态
if (state.state != CaptchaState.init) {
state = (state: CaptchaState.init, countdown: 0);
}
}
// 启动倒计时
void startCountdown() {
if (state.state == CaptchaState.counting) {
return;
}
// 取消之前的定时器
_timer?.cancel();
// 初始化状态
state = (state: CaptchaState.counting, countdown: 5);
// 创建周期性定时器
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
final newCount = state.countdown - 1;
if (newCount <= 0) {
timer.cancel();
// 更新状态为可重新发送
state = (state: CaptchaState.restart, countdown: 0);
return;
}
// 更新倒计时状态
state = (state: CaptchaState.counting, countdown: newCount);
});
}
}
调用
dart
final captchaState = ref.watch(captchaProvider(Homepage.sName),);
final notifier = ref.read(captchaProvider(Homepage.sName).notifier,);
notifier.startCountdown();
3.总结
写到这里虽然没写多少东西,但是脑子已经累了。实践是检验真理的唯一标准,真正实践Riverpod
的应用才浅浅的知道了该怎么用这个东西。大概总结一下吧,脑子已经乱了。对于单一数据获取,不去人工更改数据的话,函数注解生成代码
已经够了,但是感觉大多数场景满足不了的吧。所以我们还是主要选择类注解生成代码
,新版本的用法就是要让我们把数据的build
获取以及数据的改变逻辑都放在类中,做到逻辑业务与UI展示分离,降低代码的耦合度,真的挺方便规范开发的。其实看了上述例子,其实我们就可以把类注解生成代码
中的build
当做函数注解生成代码
就更容易理解了。生成的Provider
类型完全就是和构造函数的返回值以及传入的参数有关。