提示词优化器
使用Flutter + Riverpod 开发了提示词优化器
Riverpod知识点简单记录一下
Flutter 状态管理方案众多(Provider、BLoC、GetX、Riverpod),但大多数存在以下问题:
- 编译时安全性不足:Provider 使用
BuildContext,容易出现运行时错误 - 依赖管理混乱:全局单例导致测试困难
- 样板代码过多:手动创建 Provider 繁琐且易出错
核心原理
Riverpod vs Provider:为什么升级?
| 特性 | Provider | Riverpod |
|---|---|---|
| 依赖 BuildContext | ✅ 是 | ❌ 否 |
| 编译时安全 | ❌ 否 | ✅ 是 |
| 代码生成 | ❌ 不支持 | ✅ 支持 |
| 测试友好 | 中 | 高 |
| Provider 组合 | 困难 | 简单 |
| 性能 | 中 | 高 |
核心优势:
- 无需 BuildContext:可以在任何地方读取 Provider
- 编译时检查:类型错误在编译期发现,而非运行时
- 自动依赖追踪:Provider 依赖关系自动管理
- 更好的性能:精确重建,减少不必要的 Widget 重建
Riverpod 的三种 Provider 类型
// 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):
// ❌ 样板代码多,易出错
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 注解):
// ✅ 简洁、类型安全、自动生成 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):
// 自动生成,无需手动编写
final settingsNotifierProvider =
StateNotifierProvider.autoDispose<SettingsNotifier, AppSettings>((ref) {
return SettingsNotifier()..ref = ref;
});
代码生成的优势
- ✅ 减少 50% 样板代码
- ✅ 自动推断 Provider 类型
- ✅ 自动管理依赖关系
- ✅ 编译时检查依赖循环
实战代码:设置管理的完整实现
1. 定义状态实体
// 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(数据层)
// 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
// 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 实战
问题场景
在实际项目中,我们需要:
- 开发环境:使用真实的数据库和 API
- 测试环境:使用 Mock 数据,避免真实网络请求
- 多环境配置:开发/测试/生产环境使用不同的配置
解决方案:ProviderScope.overrides
// 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(),
),
);
}
测试中的依赖注入
// 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 手动管理
// 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 生命周期
@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 的正确用法
问题:过度重建
// ❌ 错误:监听整个状态,任何字段变化都会重建
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(optimizationProvider);
// 只使用了 status 字段,但其他字段变化也会触发重建
return Text(state.status.toString());
}
}
解决方案:使用 select 精确监听
// ✅ 正确:只监听 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());
}
}
性能对比
// 场景:状态有 10 个字段,每秒更新 1 个字段
// ❌ 使用 watch:每秒重建 10 次
final state = ref.watch(provider);
// ✅ 使用 select:每秒重建 1 次
final status = ref.watch(provider.select((s) => s.status));
Provider 组合:依赖其他 Provider
简单依赖
// 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
@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();
}
}
依赖循环检测
// ❌ 错误:依赖循环(编译时报错)
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 未找到
错误信息:
ProviderNotFoundException: No provider found for settingsProvider
原因:未在 ProviderScope 中包裹应用
解决方案:
void main() {
runApp(
ProviderScope( // ← 必须包裹
child: MyApp(),
),
);
}
问题 2:代码生成失败
错误信息:
Could not find part file 'settings_provider.g.dart'
解决方案:
# 运行代码生成
dart run build_runner build --delete-conflicting-outputs
# 或持续监听
dart run build_runner watch --delete-conflicting-outputs
问题 3:状态未更新
错误写法:
// ❌ 使用 read 监听状态(不会触发重建)
final state = ref.read(settingsProvider);
正确写法:
// ✅ 使用 watch 监听状态
final state = ref.watch(settingsProvider);
规则:
ref.watch:监听状态变化,自动重建 UIref.read:一次性读取,不监听变化(用于事件处理)ref.listen:监听变化但不重建(用于副作用,如导航、Toast)
问题 4:内存泄漏
场景:在 Notifier 中订阅 Stream,但未取消订阅
解决方案:
@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 命名规范
// ✅ 推荐命名
final userProvider = ...; // 状态 Provider
final userRepositoryProvider = ...; // Repository Provider
final apiServiceProvider = ...; // Service Provider
// ❌ 不推荐
final user = ...; // 不清晰
final userProv = ...; // 缩写
2. 文件组织
lib/features/settings/
├── data/
│ └── settings_repository.dart # Repository
├── domain/
│ └── entities/
│ └── app_settings.dart # 状态实体
└── presentation/
└── providers/
├── settings_provider.dart # Provider 定义
└── settings_provider.g.dart # 生成的代码
3. 依赖声明
// ✅ 显式声明依赖(便于测试和理解)
final settingsProvider = StateNotifierProvider<SettingsNotifier, AppSettings>((
ref,
) {
return SettingsNotifier(ref.watch(settingsRepositoryProvider));
}, dependencies: [
settingsRepositoryProvider, // 显式声明依赖
settingsBoxProvider,
]);
4. 避免在 build 方法中执行副作用
// ❌ 错误:在 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();
});
}
总结
核心要点回顾
- Riverpod vs Provider:无需 BuildContext、编译时安全、更好的性能
- 代码生成:
@riverpod注解自动生成 Provider,减少样板代码 - 依赖注入:
ProviderScope.overrides实现环境隔离和测试 Mock - 性能优化:使用
select精确监听,避免过度重建 - 生命周期:合理使用
autoDispose,避免内存泄漏
Riverpod 的优势
- ✅ 类型安全:编译时检查,减少运行时错误
- ✅ 测试友好:依赖注入简化 Mock
- ✅ 性能优越:精确重建,减少不必要的计算
- ✅ 代码简洁:代码生成减少样板代码
延伸阅读
下一篇预告
《Drift 数据库实战:类型安全的 SQLite 解决方案》将深入讲解:
- Drift vs sqflite 的核心差异
- 表定义和 DAO 模式
- 数据库迁移策略
- 响应式查询的实现
提示词优化-项目源码
官方文档:
项目源码:
- 完整代码:
https://github.com/JIULANG9/PromptOptimizer - Settings Provider:
lib/features/settings/presentation/providers/settings_provider.dart