riverpod_annotation 用法与使用场景

riverpod_annotation 用法与使用场景

本文整理 riverpod_annotationriverpod_generator 的常见用法。它们用于把手写 Provider 转为注解式声明,减少样板代码,并让 Provider 命名、family、autoDispose 等能力由生成器统一处理。

1. 依赖与基本配置

当前项目已经包含相关依赖:

dependencies:
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  build_runner: ^2.4.9
  riverpod_generator: ^2.4.0

使用注解前,需要在 Dart 文件中引入:

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'example.g.dart';

然后运行代码生成:

dart run build_runner build --delete-conflicting-outputs

开发时也可以持续监听:

dart run build_runner watch --delete-conflicting-outputs

生成文件命名规则:

  • 当前文件:auth_providers.dart
  • 生成文件:auth_providers.g.dart
  • 文件内必须写:part 'auth_providers.g.dart';

2. @riverpod 的核心作用

@riverpod 可以标注两类声明:

  • 函数:生成普通 Provider、FutureProvider、StreamProvider 等。
  • 类:生成 NotifierProvider、AsyncNotifierProvider 等。

生成器会根据函数或类的返回类型推断 Provider 类型。

例如:

@riverpod
String appName(AppNameRef ref) {
  return 'Camera App';
}

会生成:

final appNameProvider = AutoDisposeProvider<String>(...);

调用方式:

final appName = ref.watch(appNameProvider);

注意:生成的 Provider 名通常是在函数名后加 Provider

3. 函数式 Provider

3.1 同步值:生成 Provider

适合提供简单只读值或派生值。

@riverpod
String apiBaseUrl(ApiBaseUrlRef ref) {
  return 'https://api.example.com';
}

使用:

final baseUrl = ref.watch(apiBaseUrlProvider);

适合场景:

  • App 名称。
  • API Base URL。
  • 环境配置。
  • 根据其他 Provider 派生出来的只读值。

3.2 依赖注入:生成 Provider

适合提供 Repository、DataSource、Client 等依赖对象。

@riverpod
ApiClient apiClient(ApiClientRef ref) {
  final baseUrl = ref.watch(apiBaseUrlProvider);
  return ApiClient(baseUrl: baseUrl);
}

@riverpod
UserRepository userRepository(UserRepositoryRef ref) {
  final client = ref.watch(apiClientProvider);
  return UserRepository(client);
}

使用:

final repository = ref.read(userRepositoryProvider);

适合场景:

  • HTTP Client。
  • Repository。
  • DataSource。
  • Logger。
  • 数据库访问对象。

3.3 Future:生成 FutureProvider

函数返回 Future<T> 时,会生成 FutureProvider<T>

@riverpod
Future<User> currentUser(CurrentUserRef ref) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchCurrentUser();
}

UI 使用:

final userAsync = ref.watch(currentUserProvider);

return userAsync.when(
  loading: () => const CircularProgressIndicator(),
  error: (error, _) => Text('加载失败:$error'),
  data: (user) => Text(user.name),
);

适合场景:

  • 页面初始化读取详情。
  • 加载远程配置。
  • 读取当前用户信息。
  • 一次性数据库查询。

3.4 Stream:生成 StreamProvider

函数返回 Stream<T> 时,会生成 StreamProvider<T>

@riverpod
Stream<List<DeviceModel>> deviceList(DeviceListRef ref) {
  final dataSource = ref.watch(deviceLocalDsProvider);
  return dataSource.watchAll();
}

使用:

final devicesAsync = ref.watch(deviceListProvider);

适合场景:

  • 数据库变化流。
  • WebSocket 消息流。
  • BLE 扫描结果。
  • 下载进度。
  • 实时设备列表。

4. family:带参数 Provider

注解函数带普通参数时,生成器会自动生成 family Provider。

@riverpod
Future<User> user(UserRef ref, String userId) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchUser(userId);
}

使用:

final userAsync = ref.watch(userProvider('u_123'));

多个参数也可以:

@riverpod
Future<List<Order>> userOrders(
  UserOrdersRef ref, {
  required String userId,
  required int page,
}) async {
  final repository = ref.watch(orderRepositoryProvider);
  return repository.fetchUserOrders(userId: userId, page: page);
}

使用:

final ordersAsync = ref.watch(
  userOrdersProvider(userId: 'u_123', page: 1),
);

适合场景:

  • 用户详情:userId
  • 设备详情:deviceId
  • 订单详情:orderId
  • 搜索结果:keywordpage
  • 播放器状态:deviceId

参数建议:

  • 使用 Stringintenum 等稳定值。
  • 如果传对象,确保对象不可变,并正确实现 ==hashCode
  • 避免传入会频繁变化的临时对象。

5. 类式 Notifier

5.1 同步状态:生成 NotifierProvider

类上使用 @riverpod,并继承生成的 _$ClassName

@riverpod
class Counter extends _$Counter {
  @override
  int build() {
    return 0;
  }

  void increment() {
    state++;
  }

  void reset() {
    state = 0;
  }
}

生成的 Provider:

counterProvider

UI 使用:

final count = ref.watch(counterProvider);

IconButton(
  onPressed: () {
    ref.read(counterProvider.notifier).increment();
  },
  icon: const Icon(Icons.add),
);

适合场景:

  • 计数器。
  • 选中状态。
  • 登录状态。
  • 扫描状态。
  • 多字段页面状态。

5.2 多字段状态

class LoginState {
  final bool isLoading;
  final String? errorMessage;
  final User? user;

  const LoginState({
    this.isLoading = false,
    this.errorMessage,
    this.user,
  });

  LoginState copyWith({
    bool? isLoading,
    String? errorMessage,
    User? user,
    bool clearError = false,
    bool clearUser = false,
  }) {
    return LoginState(
      isLoading: isLoading ?? this.isLoading,
      errorMessage: clearError ? null : errorMessage ?? this.errorMessage,
      user: clearUser ? null : user ?? this.user,
    );
  }
}

@riverpod
class Login extends _$Login {
  @override
  LoginState build() {
    return const LoginState();
  }

  Future<void> login(String username, String password) async {
    state = state.copyWith(isLoading: true, clearError: true);

    final repository = ref.read(authRepositoryProvider);
    final user = await repository.login(username, password);

    if (user == null) {
      state = state.copyWith(
        isLoading: false,
        errorMessage: '用户名或密码错误',
      );
      return;
    }

    state = state.copyWith(
      isLoading: false,
      user: user,
      clearError: true,
    );
  }

  void logout() {
    state = state.copyWith(clearUser: true, clearError: true);
  }
}

使用:

final loginState = ref.watch(loginProvider);

FilledButton(
  onPressed: loginState.isLoading
      ? null
      : () {
          ref.read(loginProvider.notifier).login(username, password);
        },
  child: const Text('登录'),
);

推荐配合 freezed 简化 State:

@freezed
class LoginState with _$LoginState {
  const factory LoginState({
    @Default(false) bool isLoading,
    String? errorMessage,
    User? user,
  }) = _LoginState;
}

6. 类式 AsyncNotifier

如果 build 返回 Future<T>,生成器会生成异步 Notifier Provider。

@riverpod
class UserList extends _$UserList {
  @override
  Future<List<User>> build() async {
    final repository = ref.watch(userRepositoryProvider);
    return repository.fetchUsers();
  }

  Future<void> refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      final repository = ref.read(userRepositoryProvider);
      return repository.fetchUsers();
    });
  }
}

UI 使用:

final usersAsync = ref.watch(userListProvider);

return usersAsync.when(
  loading: () => const CircularProgressIndicator(),
  error: (error, _) => Text('加载失败:$error'),
  data: (users) => UserListView(users: users),
);

适合场景:

  • 初始化需要请求接口。
  • 页面有刷新动作。
  • 列表分页。
  • 异步增删改后刷新状态。

常见写法:新增一条数据后刷新列表。

Future<void> addUser(String name) async {
  final repository = ref.read(userRepositoryProvider);
  await repository.createUser(name);
  ref.invalidateSelf();
}

ref.invalidateSelf() 会让当前 Provider 重新执行 build

7. 类式 StreamNotifier

如果 build 返回 Stream<T>,适合表达持续变化的数据。

@riverpod
class Messages extends _$Messages {
  @override
  Stream<List<Message>> build(String roomId) {
    final repository = ref.watch(messageRepositoryProvider);
    return repository.watchMessages(roomId);
  }
}

使用:

final messagesAsync = ref.watch(messagesProvider('room_1'));

适合场景:

  • 聊天消息。
  • 设备在线状态。
  • 数据库监听。
  • 实时日志。

如果只是简单返回一个 Stream,函数式 @riverpod 通常更简洁。需要额外方法时,再使用类式写法。

8. keepAlive 与自动销毁

使用 @riverpod 默认会生成 autoDispose Provider。也就是说,当没有监听者时,Provider 会自动销毁。

如果希望 Provider 长期保留,可以使用 @Riverpod(keepAlive: true)

@Riverpod(keepAlive: true)
ApiClient apiClient(ApiClientRef ref) {
  return ApiClient();
}

适合 keepAlive: true 的场景:

  • 全局配置。
  • API Client。
  • 数据库连接。
  • SharedPreferences。
  • 登录状态。

适合默认 autoDispose 的场景:

  • 页面详情数据。
  • 搜索结果。
  • 临时表单状态。
  • 按 ID 加载的详情。

也可以在 Provider 内按条件保活:

@riverpod
Future<Config> remoteConfig(RemoteConfigRef ref) async {
  final link = ref.keepAlive();
  final config = await fetchRemoteConfig();
  return config;
}

如果后续需要释放:

link.close();

9. dependencies 参数

@Riverpod 支持声明依赖,用于配合 scoped override 约束依赖关系。

@Riverpod(dependencies: [currentUser])
Future<List<Project>> userProjects(UserProjectsRef ref) async {
  final user = await ref.watch(currentUserProvider.future);
  final repository = ref.watch(projectRepositoryProvider);
  return repository.fetchProjects(user.id);
}

适合场景:

  • 需要明确某个 Provider 依赖可被局部覆盖的 Provider。
  • 大型项目中限制 scoped Provider 的依赖边界。

中小型项目可以先不使用,等 Provider 层级复杂后再引入。

10. 生成代码后的命名

常见命名规则:

声明 生成 Provider
String appName(AppNameRef ref) appNameProvider
Future<User> user(UserRef ref, String id) userProvider(id)
class Counter extends _$Counter counterProvider
class UserList extends _$UserList userListProvider

生成器还会生成对应的 Ref 类型:

@riverpod
String appName(AppNameRef ref) => 'Camera App';

这里的 AppNameRef 由生成器生成。第一次写代码时 IDE 可能报错,运行 build_runner 后会恢复。

11. 从手写 Provider 迁移到注解

11.1 Provider 迁移

手写:

final apiClientProvider = Provider<ApiClient>((ref) {
  return ApiClient();
});

注解:

@Riverpod(keepAlive: true)
ApiClient apiClient(ApiClientRef ref) {
  return ApiClient();
}

使用侧从:

ref.watch(apiClientProvider);

保持不变,仍然是:

ref.watch(apiClientProvider);

11.2 NotifierProvider 迁移

手写:

class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;

  void increment() => state++;
}

final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);

注解:

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
}

使用侧:

ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();

12. 与 flutter_riverpod 的关系

flutter_riverpod 是运行时库,提供:

  • ProviderScope
  • ConsumerWidget
  • ConsumerStatefulWidget
  • WidgetRef
  • ref.watch / ref.read

riverpod_annotation 是注解库,提供:

  • @riverpod
  • @Riverpod(...)

riverpod_generator 是生成器,负责读取注解并生成 .g.dart

三者关系:

flutter_riverpod      运行时使用
riverpod_annotation   写注解
riverpod_generator    build_runner 生成代码

13. 使用场景选择

场景 推荐写法
简单只读值 函数式 @riverpod
Repository / DataSource 注入 函数式 @riverpod + keepAlive: true
一次性异步请求 返回 Future<T> 的函数式 @riverpod
持续数据流 返回 Stream<T> 的函数式 @riverpod
简单同步可变状态 类式 @riverpodbuild 返回同步值
复杂页面状态 类式 @riverpod + State 类
异步初始化和刷新 类式 @riverpodbuild 返回 Future<T>
按 ID 加载数据 参数化函数或类,自动生成 family
全局长期依赖 @Riverpod(keepAlive: true)

14. 常见错误

14.1 忘记 part

错误:

import 'package:riverpod_annotation/riverpod_annotation.dart';

@riverpod
String appName(AppNameRef ref) => 'App';

缺少:

part 'file_name.g.dart';

14.2 忘记运行 build_runner

现象:

  • _$Counter 找不到。
  • AppNameRef 找不到。
  • appNameProvider 找不到。

处理:

dart run build_runner build --delete-conflicting-outputs

14.3 文件名和 part 名不一致

文件名是 auth_providers.dart,则必须写:

part 'auth_providers.g.dart';

14.4 在注解 Notifier 中忘记继承生成类

错误:

@riverpod
class Counter {
  int build() => 0;
}

正确:

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
}

14.5 把所有 Provider 都 keepAlive

keepAlive: true 会让 Provider 不再自动销毁。页面级数据、搜索结果、临时状态不建议长期保留。

推荐:

  • 全局依赖用 keepAlive: true
  • 页面状态默认 autoDispose。
  • 需要缓存时再单独使用 ref.keepAlive()

15. 本项目可迁移示例

当前项目主要使用手写 Provider。以下是可迁移方向。

15.1 全局依赖

现状:

final databaseProvider = Provider<Database>((ref) => AppProviders.db);

注解版:

@Riverpod(keepAlive: true)
Database database(DatabaseRef ref) {
  return AppProviders.db;
}

15.2 设备列表

现状:

final deviceListProvider = StreamProvider<List<DeviceModel>>((ref) {
  return ref.watch(deviceLocalDsProvider).watchAll();
});

注解版:

@riverpod
Stream<List<DeviceModel>> deviceList(DeviceListRef ref) {
  return ref.watch(deviceLocalDsProvider).watchAll();
}

15.3 BLE 状态

现状:

final bleStateProvider = NotifierProvider<BleNotifier, BleState>(
  BleNotifier.new,
);

注解版:

@riverpod
class BleStateController extends _$BleStateController {
  @override
  BleState build() {
    return const BleState();
  }

  Future<void> startScan() async {
    // 扫描逻辑
  }
}

迁移建议:

  • 不必一次性迁移全项目。
  • 新模块可以优先使用注解式 Provider。
  • 旧模块保持稳定,等有重构需求时再迁移。
posted @ 2026-05-18 10:51  呢哇哦比较  阅读(55)  评论(0)    收藏  举报