基于Riverpod Async Notifier Provider实现启动时加载本地存储语言
问题描述
我已在应用中实现本地化功能,支持用户选择语言并保存到本地存储。需求如下:
- 应用启动时从本地存储加载语言,传递给
MaterialApp.router设置首选Locale - 使用Riverpod Async Notifier Provider存储语言选择,实现切换语言时自动刷新应用
当前实现虽能运行,但存在不合理之处:采用GoRouter的URL路由,尝试参考教程做初始化时,因在App Widget中监听currentLanguageProvider,导致该Provider在获取值前就被访问,无法使用子Widget方案。
当前代码实现
app.dart
class App extends ConsumerWidget { const App({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final goRouter = ref.watch(goRouterProvider); final language = ref.watch(currentLanguageProvider).asData?.value; return MaterialApp.router( routerConfig: goRouter, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: language != null ? Locale(language.code) : null, ); } }
current_language_provider.dart
@Riverpod(keepAlive: true) class CurrentLanguage extends _$CurrentLanguage { Future<LanguageEnum> _getLanguage() async { return await ref.read(getLanguageProvider.future); } @override FutureOr<LanguageEnum> build() async { return await _getLanguage(); } void set(LanguageEnum value) { state = AsyncValue.data(value); } }
language_repository.dart
class LanguageRepository { final Ref ref; LanguageRepository(this.ref); final storage = const FlutterSecureStorage(); /// 存储语言到本地安全存储 Future<void> setLanguage(LanguageEnum language) async { await storage.write(key: 'language', value: language.code); ref.read(currentLanguageProvider.notifier).set(language); } /// 从本地安全存储获取语言 Future<LanguageEnum> getLanguage() async { final code = await storage.read(key: 'language'); // 未设置语言时,返回默认语言并存储 if (code == null) { const defaultLanguage = LanguageEnum.eng; await setLanguage(defaultLanguage); return defaultLanguage; } for (var value in LanguageEnum.values) { if (value.code == code) { ref.read(currentLanguageProvider.notifier).set(value); return value; } } return LanguageEnum.eng; } } // LanguageRepository的Riverpod Provider @Riverpod(keepAlive: true) LanguageRepository languageRepository(LanguageRepositoryRef ref) { return LanguageRepository(ref); } @Riverpod(keepAlive: true) Future<LanguageEnum> getLanguage(GetLanguageRef ref) { return ref.read(languageRepositoryProvider).getLanguage(); }
side_drawer.dart
class SideDrawer extends StatelessWidget { const SideDrawer({super.key}); @override Widget build(BuildContext context) { return SafeArea( child: Drawer( child: Column( children: [ Consumer(builder: (BuildContext context, WidgetRef ref, Widget? child) { final language = ref.watch(currentLanguageProvider); return AsyncValueWidget( value: language, data: (currentLanguage) => DropdownButton( value: currentLanguage, items: [ for (var eachLanguage in LanguageEnum.values) DropdownMenuItem( value: eachLanguage, child: Text(eachLanguage.name), ), ], onChanged: (value) async { if (value != null) { await ref.read(localizationControllerProvider.notifier).setLanguage(value); } }, ), ); }), ], ), ), ); } }
localization_controller.dart
@riverpod class LocalizationController extends _$LocalizationController { @override FutureOr<void> build() async { return; } Future<void> setLanguage(LanguageEnum language) async { final languageRepo = ref.read(languageRepositoryProvider); await languageRepo.setLanguage(language); } }
优化方案
1. 重构CurrentLanguage Provider,消除冗余依赖
移除中间的getLanguageProvider,直接在CurrentLanguage的build方法中调用Repository获取语言,避免循环依赖和提前访问问题:
@Riverpod(keepAlive: true) class CurrentLanguage extends _$CurrentLanguage { @override FutureOr<LanguageEnum> build() async { // 直接从Repository获取初始语言 return await ref.read(languageRepositoryProvider).getLanguage(); } Future<void> set(LanguageEnum value) async { // 先更新存储,再更新状态 await ref.read(languageRepositoryProvider).setLanguage(value); state = AsyncValue.data(value); } }
2. 调整LanguageRepository,移除不必要的Provider调用
去掉getLanguage和setLanguage中对currentLanguageProvider的直接调用,把状态更新的职责交给CurrentLanguage Notifier:
class LanguageRepository { final Ref ref; LanguageRepository(this.ref); final storage = const FlutterSecureStorage(); Future<void> setLanguage(LanguageEnum language) async { await storage.write(key: 'language', value: language.code); // 移除这里的set调用,交给CurrentLanguage处理 } Future<LanguageEnum> getLanguage() async { final code = await storage.read(key: 'language'); if (code == null) { const defaultLanguage = LanguageEnum.eng; await setLanguage(defaultLanguage); return defaultLanguage; } for (var value in LanguageEnum.values) { if (value.code == code) { return value; } } return LanguageEnum.eng; } } // 删除冗余的getLanguageProvider
3. 修改App Widget,正确处理AsyncValue状态
在App Widget中使用AsyncValue.when处理加载、错误、成功状态,确保语言加载完成后再渲染MaterialApp.router,避免locale为null的情况:
class App extends ConsumerWidget { const App({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final goRouter = ref.watch(goRouterProvider); final languageState = ref.watch(currentLanguageProvider); return languageState.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, stack) => const Center(child: Text('加载语言失败')), data: (language) { return MaterialApp.router( routerConfig: goRouter, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: Locale(language.code), ); }, ); } }
4. 优化语言切换逻辑
调整LocalizationController,直接调用CurrentLanguage Notifier的set方法,简化流程:
@riverpod class LocalizationController extends _$LocalizationController { @override FutureOr<void> build() async {} Future<void> setLanguage(LanguageEnum language) async { await ref.read(currentLanguageProvider.notifier).set(language); } }
内容的提问来源于stack exchange,提问作者spehj
相关产品推荐
相关产品推荐

