Riverpod 2.0 代码生成与依赖注入

提示词优化器

使用Flutter + Riverpod 开发了提示词优化器

项目源码JIULANG9/PromptOptimizer

Riverpod知识点简单记录一下

Flutter 状态管理方案众多(Provider、BLoC、GetX、Riverpod),但大多数存在以下问题:

  • 编译时安全性不足 :Provider 使用 BuildContext,容易出现运行时错误
  • 依赖管理混乱:全局单例导致测试困难
  • 样板代码过多:手动创建 Provider 繁琐且易出错

核心原理

Riverpod vs Provider:为什么升级?

特性 Provider Riverpod
依赖 BuildContext ✅ 是 ❌ 否
编译时安全 ❌ 否 ✅ 是
代码生成 ❌ 不支持 ✅ 支持
测试友好
Provider 组合 困难 简单
性能

核心优势

  1. 无需 BuildContext:可以在任何地方读取 Provider
  2. 编译时检查:类型错误在编译期发现,而非运行时
  3. 自动依赖追踪:Provider 依赖关系自动管理
  4. 更好的性能:精确重建,减少不必要的 Widget 重建

Riverpod 的三种 Provider 类型

dart 复制代码
// 1. Provider:不可变数据(配置、常量)
final configProvider = Provider<Config>((ref) => Config());

// 2. StateProvider:简单可变状态(计数器、开关)
final counterProvider = StateProvider<int>((ref) => 0);

// 3. StateNotifierProvider:复杂状态管理(MVI 架构)
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
  return UserNotifier(ref.watch(apiProvider));
});

@riverpod 注解:自动生成 Provider

传统写法 vs 代码生成

传统写法(手动创建 Provider)

dart 复制代码
// ❌ 样板代码多,易出错
class SettingsNotifier extends StateNotifier<AppSettings> {
  SettingsNotifier(this._repository) : super(const AppSettings());
  
  final SettingsRepository _repository;
  
  void setThemeMode(ThemeModeSetting mode) {
    state = state.copyWith(themeMode: mode);
  }
}

// 手动定义 Provider
final settingsProvider = StateNotifierProvider<SettingsNotifier, AppSettings>((ref) {
  return SettingsNotifier(ref.watch(settingsRepositoryProvider));
});

代码生成写法(使用 @riverpod 注解)

dart 复制代码
// ✅ 简洁、类型安全、自动生成 Provider
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'settings_provider.g.dart';

@riverpod
class SettingsNotifier extends _$SettingsNotifier {
  @override
  AppSettings build() {
    // 初始化状态
    return _repository.getSettings();
  }
  
  // 依赖自动注入
  SettingsRepository get _repository => ref.watch(settingsRepositoryProvider);
  
  void setThemeMode(ThemeModeSetting mode) {
    state = state.copyWith(themeMode: mode);
  }
}

// Provider 自动生成:settingsNotifierProvider

生成的代码settings_provider.g.dart):

dart 复制代码
// 自动生成,无需手动编写
final settingsNotifierProvider = 
    StateNotifierProvider.autoDispose<SettingsNotifier, AppSettings>((ref) {
  return SettingsNotifier()..ref = ref;
});

代码生成的优势

  • ✅ 减少 50% 样板代码
  • ✅ 自动推断 Provider 类型
  • ✅ 自动管理依赖关系
  • ✅ 编译时检查依赖循环

实战代码:设置管理的完整实现

1. 定义状态实体

dart 复制代码
// lib/features/settings/domain/entities/app_settings.dart

/// 应用设置(不可变状态)
class AppSettings {
  final ThemeModeSetting themeMode;
  final String locale;

  const AppSettings({
    this.themeMode = ThemeModeSetting.system,
    this.locale = 'zh',
  });

  AppSettings copyWith({
    ThemeModeSetting? themeMode,
    String? locale,
  }) {
    return AppSettings(
      themeMode: themeMode ?? this.themeMode,
      locale: locale ?? this.locale,
    );
  }
}

enum ThemeModeSetting { light, dark, system }

2. 实现 Repository(数据层)

dart 复制代码
// lib/features/settings/data/settings_repository.dart

import 'package:hive_flutter/hive_flutter.dart';

class SettingsRepository {
  final Box _box;

  SettingsRepository(this._box);

  /// 获取设置
  AppSettings getSettings() {
    final themeModeStr = _box.get('theme_mode', defaultValue: 'system');
    final locale = _box.get('locale', defaultValue: 'zh');

    return AppSettings(
      themeMode: ThemeModeSetting.values.firstWhere(
        (e) => e.name == themeModeStr,
        orElse: () => ThemeModeSetting.system,
      ),
      locale: locale,
    );
  }

  /// 保存主题模式
  Future<void> saveThemeMode(ThemeModeSetting mode) async {
    await _box.put('theme_mode', mode.name);
  }

  /// 保存语言
  Future<void> saveLocale(String locale) async {
    await _box.put('locale', locale);
  }
}

3. 使用 @riverpod 创建 Provider

dart 复制代码
// lib/features/settings/presentation/providers/settings_provider.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';

import '../../data/settings_repository.dart';
import '../../domain/entities/app_settings.dart';

/// 设置状态 Notifier(MVI Intent 处理器)
class SettingsNotifier extends StateNotifier<AppSettings> {
  final SettingsRepository _repository;

  SettingsNotifier(this._repository) : super(const AppSettings()) {
    _loadSettings();
  }

  void _loadSettings() {
    state = _repository.getSettings();
  }

  /// Intent: 切换主题模式
  Future<void> setThemeMode(ThemeModeSetting mode) async {
    await _repository.saveThemeMode(mode);
    state = state.copyWith(themeMode: mode);
  }

  /// Intent: 切换语言
  Future<void> setLocale(String locale) async {
    await _repository.saveLocale(locale);
    state = state.copyWith(locale: locale);
  }

  /// 将 ThemeModeSetting 转换为 Flutter ThemeMode
  ThemeMode get flutterThemeMode {
    switch (state.themeMode) {
      case ThemeModeSetting.light:
        return ThemeMode.light;
      case ThemeModeSetting.dark:
        return ThemeMode.dark;
      case ThemeModeSetting.system:
        return ThemeMode.system;
    }
  }

  /// 获取当前 Locale
  Locale get currentLocale => Locale(state.locale);
}

// ─── Providers ───

/// Hive Settings Box Provider(由 main.dart 中 ProviderScope override 注入)
final settingsBoxProvider = Provider<Box>((ref) {
  throw UnimplementedError('settingsBoxProvider must be overridden');
});

/// 设置仓库 Provider
final settingsRepositoryProvider = Provider<SettingsRepository>((ref) {
  return SettingsRepository(ref.watch(settingsBoxProvider));
}, dependencies: [settingsBoxProvider]);

/// 设置状态 Provider
final settingsProvider = StateNotifierProvider<SettingsNotifier, AppSettings>((
  ref,
) {
  return SettingsNotifier(ref.watch(settingsRepositoryProvider));
}, dependencies: [settingsRepositoryProvider, settingsBoxProvider]);

依赖注入:ProviderScope.overrides 实战

问题场景

在实际项目中,我们需要:

  1. 开发环境:使用真实的数据库和 API
  2. 测试环境:使用 Mock 数据,避免真实网络请求
  3. 多环境配置:开发/测试/生产环境使用不同的配置

解决方案:ProviderScope.overrides

dart 复制代码
// lib/main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 初始化 Hive
  await Hive.initFlutter('hive_data');
  final settingsBox = await Hive.openBox('app_settings_ui');

  // 初始化数据库
  final database = AppDatabase();

  runApp(
    ProviderScope(
      // 依赖注入:覆盖默认 Provider 实现
      overrides: [
        // 注入 Hive Box
        settingsBoxProvider.overrideWithValue(settingsBox),
        
        // 注入数据库实例
        databaseProvider.overrideWithValue(database),
      ],
      child: const App(),
    ),
  );
}

测试中的依赖注入

dart 复制代码
// test/settings_provider_test.dart

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mockito/mockito.dart';

class MockSettingsRepository extends Mock implements SettingsRepository {}

void main() {
  test('setThemeMode should update state and persist', () async {
    // 1. 创建 Mock Repository
    final mockRepo = MockSettingsRepository();
    when(mockRepo.getSettings()).thenReturn(const AppSettings());
    when(mockRepo.saveThemeMode(any)).thenAnswer((_) async {});

    // 2. 创建 ProviderContainer 并注入 Mock
    final container = ProviderContainer(
      overrides: [
        settingsRepositoryProvider.overrideWithValue(mockRepo),
      ],
    );

    // 3. 获取 Notifier 并执行操作
    final notifier = container.read(settingsProvider.notifier);
    await notifier.setThemeMode(ThemeModeSetting.dark);

    // 4. 验证状态更新
    expect(
      container.read(settingsProvider).themeMode,
      ThemeModeSetting.dark,
    );

    // 5. 验证持久化调用
    verify(mockRepo.saveThemeMode(ThemeModeSetting.dark)).called(1);
  });
}

关键点

  • ✅ 使用 ProviderContainer 而非 ProviderScope(测试环境)
  • ✅ 通过 overrides 注入 Mock 对象
  • ✅ 验证状态更新和副作用(持久化调用)

Provider 生命周期管理

自动销毁 vs 手动管理

dart 复制代码
// 1. 自动销毁(autoDispose)
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  
  void increment() => state++;
}
// 生成:counterProvider(当无监听者时自动销毁)

// 2. 保持存活(不使用 autoDispose)
@Riverpod(keepAlive: true)
class GlobalConfig extends _$GlobalConfig {
  @override
  Config build() => Config();
}
// 生成:globalConfigProvider(永不销毁)

何时使用 autoDispose?

场景 使用 autoDispose 原因
页面级状态 ✅ 是 离开页面后释放内存
全局配置 ❌ 否 需要持久存在
网络请求 ✅ 是 避免内存泄漏
数据库连接 ❌ 否 频繁创建销毁影响性能

监听 Provider 生命周期

dart 复制代码
@riverpod
class UserNotifier extends _$UserNotifier {
  @override
  UserState build() {
    // 监听销毁事件
    ref.onDispose(() {
      print('UserNotifier disposed');
      _subscription?.cancel();
    });
    
    // 监听添加监听者
    ref.onAddListener(() {
      print('Listener added');
    });
    
    // 监听移除监听者
    ref.onRemoveListener(() {
      print('Listener removed');
    });
    
    return const UserState();
  }
}

性能优化:select 与 watch 的正确用法

问题:过度重建

dart 复制代码
// ❌ 错误:监听整个状态,任何字段变化都会重建
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(optimizationProvider);
    
    // 只使用了 status 字段,但其他字段变化也会触发重建
    return Text(state.status.toString());
  }
}

解决方案:使用 select 精确监听

dart 复制代码
// ✅ 正确:只监听 status 字段
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final status = ref.watch(
      optimizationProvider.select((s) => s.status),
    );
    
    // 只有 status 变化时才会重建
    return Text(status.toString());
  }
}

性能对比

dart 复制代码
// 场景:状态有 10 个字段,每秒更新 1 个字段

// ❌ 使用 watch:每秒重建 10 次
final state = ref.watch(provider);

// ✅ 使用 select:每秒重建 1 次
final status = ref.watch(provider.select((s) => s.status));

Provider 组合:依赖其他 Provider

简单依赖

dart 复制代码
// Provider A:API 配置
final apiConfigProvider = Provider<ApiConfig>((ref) {
  return ApiConfig(baseUrl: 'https://api.example.com');
});

// Provider B:依赖 Provider A
final apiServiceProvider = Provider<ApiService>((ref) {
  final config = ref.watch(apiConfigProvider);
  return ApiService(config);
});

// Provider C:依赖 Provider B
final userRepositoryProvider = Provider<UserRepository>((ref) {
  final apiService = ref.watch(apiServiceProvider);
  return UserRepository(apiService);
});

复杂依赖:多个 Provider

dart 复制代码
@riverpod
class OptimizationNotifier extends _$OptimizationNotifier {
  @override
  OptimizationState build() {
    // 依赖多个 Provider
    final useCase = ref.watch(optimizePromptUseCaseProvider);
    final settingsRepo = ref.watch(settingsRepositoryProvider);
    final apiConfigRepo = ref.watch(apiConfigRepositoryProvider);
    final templateRepo = ref.watch(templateRepositoryProvider);
    
    return const OptimizationState();
  }
}

依赖循环检测

dart 复制代码
// ❌ 错误:依赖循环(编译时报错)
final providerA = Provider((ref) {
  ref.watch(providerB);  // A 依赖 B
  return 'A';
});

final providerB = Provider((ref) {
  ref.watch(providerA);  // B 依赖 A → 循环依赖!
  return 'B';
});

// 编译错误:Circular dependency detected

实践经验:常见问题与解决方案

问题 1:Provider 未找到

错误信息

yaml 复制代码
ProviderNotFoundException: No provider found for settingsProvider

原因 :未在 ProviderScope 中包裹应用

解决方案

dart 复制代码
void main() {
  runApp(
    ProviderScope(  // ← 必须包裹
      child: MyApp(),
    ),
  );
}

问题 2:代码生成失败

错误信息

arduino 复制代码
Could not find part file 'settings_provider.g.dart'

解决方案

bash 复制代码
# 运行代码生成
dart run build_runner build --delete-conflicting-outputs

# 或持续监听
dart run build_runner watch --delete-conflicting-outputs

问题 3:状态未更新

错误写法

dart 复制代码
// ❌ 使用 read 监听状态(不会触发重建)
final state = ref.read(settingsProvider);

正确写法

dart 复制代码
// ✅ 使用 watch 监听状态
final state = ref.watch(settingsProvider);

规则

  • ref.watch:监听状态变化,自动重建 UI
  • ref.read:一次性读取,不监听变化(用于事件处理)
  • ref.listen:监听变化但不重建(用于副作用,如导航、Toast)

问题 4:内存泄漏

场景:在 Notifier 中订阅 Stream,但未取消订阅

解决方案

dart 复制代码
@riverpod
class DataNotifier extends _$DataNotifier {
  StreamSubscription? _subscription;
  
  @override
  DataState build() {
    // 监听销毁事件,取消订阅
    ref.onDispose(() {
      _subscription?.cancel();
    });
    
    _subscription = dataStream.listen((data) {
      state = data;
    });
    
    return const DataState();
  }
}

最佳实践

1. Provider 命名规范

dart 复制代码
// ✅ 推荐命名
final userProvider = ...;              // 状态 Provider
final userRepositoryProvider = ...;    // Repository Provider
final apiServiceProvider = ...;        // Service Provider

// ❌ 不推荐
final user = ...;                      // 不清晰
final userProv = ...;                  // 缩写

2. 文件组织

bash 复制代码
lib/features/settings/
├── data/
│   └── settings_repository.dart       # Repository
├── domain/
│   └── entities/
│       └── app_settings.dart          # 状态实体
└── presentation/
    └── providers/
        ├── settings_provider.dart     # Provider 定义
        └── settings_provider.g.dart   # 生成的代码

3. 依赖声明

dart 复制代码
// ✅ 显式声明依赖(便于测试和理解)
final settingsProvider = StateNotifierProvider<SettingsNotifier, AppSettings>((
  ref,
) {
  return SettingsNotifier(ref.watch(settingsRepositoryProvider));
}, dependencies: [
  settingsRepositoryProvider,  // 显式声明依赖
  settingsBoxProvider,
]);

4. 避免在 build 方法中执行副作用

dart 复制代码
// ❌ 错误:在 build 中执行异步操作
@override
Widget build(BuildContext context, WidgetRef ref) {
  ref.read(userProvider.notifier).loadUser();  // 每次重建都会调用
  return Container();
}

// ✅ 正确:在 initState 或事件处理中执行
@override
void initState() {
  super.initState();
  Future.microtask(() {
    ref.read(userProvider.notifier).loadUser();
  });
}

总结

核心要点回顾

  1. Riverpod vs Provider:无需 BuildContext、编译时安全、更好的性能
  2. 代码生成@riverpod 注解自动生成 Provider,减少样板代码
  3. 依赖注入ProviderScope.overrides 实现环境隔离和测试 Mock
  4. 性能优化 :使用 select 精确监听,避免过度重建
  5. 生命周期 :合理使用 autoDispose,避免内存泄漏

Riverpod 的优势

  • ✅ 类型安全:编译时检查,减少运行时错误
  • ✅ 测试友好:依赖注入简化 Mock
  • ✅ 性能优越:精确重建,减少不必要的计算
  • ✅ 代码简洁:代码生成减少样板代码

延伸阅读

下一篇预告

《Drift 数据库实战:类型安全的 SQLite 解决方案》将深入讲解:

  • Drift vs sqflite 的核心差异
  • 表定义和 DAO 模式
  • 数据库迁移策略
  • 响应式查询的实现

提示词优化-项目源码

官方文档

项目源码

  • 完整代码:https://github.com/JIULANG9/PromptOptimizer
  • Settings Provider:lib/features/settings/presentation/providers/settings_provider.dart
相关推荐
空白诗2 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、傅里叶变换与频谱:从时域到频域的视觉翻译
flutter
2601_949593652 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、极坐标对称投影:万花筒般的几何韵律
flutter·华为·harmonyos
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— Dart 层源码逐行解析
flutter
心之语歌2 小时前
Flutter Provider 使用教程:Consumer/of/watch/read 全解析
flutter
2601_949593652 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、Voronoi 泰森多边形:空间分割的动态演化
flutter·华为·harmonyos
松叶似针2 小时前
Flutter三方库适配OpenHarmony【doc_text】— Word 文档解析插件功能全景与适配价值
flutter·word·harmonyos
2601_949593653 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、分布式联觉震动:鸿蒙多端同步的节奏共鸣
flutter·harmonyos
空白诗3 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、着色器与GPU编程:高性能视觉渲染
flutter·harmonyos
2601_949593653 小时前
Harmony Flutter 跨平台开发实战:鸿蒙与音乐律动艺术、元胞自动机:生命游戏的音频演化逻辑
flutter