flutter_riverpod: ^2.6.1 应用笔记 (一)

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_annotationfreezed两个插件。因为Riverpod内部会通过 == 来比较state值的变化,从而更新UI;而普通自定义类默认 == 比较的是引用地址。

所以,状态类型是 简单类型(int、double、String、bool、List/Map 等不可变集合或者类内部没有比较需求,只关心对象引用,就不需要用这两个插件。freezed 的主要作用是生成不可变对象和内容比较逻辑,解决复杂状态的深度比较问题。

接下来是我对flutter_riverpod注解生成代码的一些实践以及实践中的理解:不同于GetXpub.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,要用类式 ProviderNotifier 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类型完全就是和构造函数的返回值以及传入的参数有关。

相关推荐
gAlAxy...16 分钟前
深入理解 Cookie 与 Session —— Web 状态保持详解与实战
前端
专注VB编程开发20年22 分钟前
c#,vb.net全局多线程锁,可以在任意模块或类中使用,但尽量用多个锁提高效率
java·前端·数据库·c#·.net
JarvanMo26 分钟前
Google Connect 8月14日纪实
前端
猩猩程序员1 小时前
Go 1.24 全面拥抱 Swiss Table:让内置 map 提速 60% 的秘密
前端
1024小神1 小时前
vue3 + vite项目,如果在build的时候对代码加密混淆
前端·javascript
轻语呢喃1 小时前
useRef :掌握 DOM 访问与持久化状态的利器
前端·javascript·react.js
wwy_frontend2 小时前
useState 的 9个常见坑与最佳实践
前端·react.js
Jerry2 小时前
Compose 界面工具包
前端
Focusbe2 小时前
从0到1开发一个AI助手
前端·人工智能·面试