Flutter中LocaleResolutionCallback内使用Riverpod报错的解决方案咨询
我在Flutter项目中使用riverpod_hook做依赖注入,需要根据移动设备的语言设置同步修改应用语言,于是在MaterialApp的localeResolutionCallback中通过ref更新localeCodeProvider的状态,但运行时触发报错:Tried to modify a provider while the widget tree was building。要求必须使用localeResolutionCallback实现全局语言适配,求正确结合Provider的解决方案。
原代码
app.dart
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class Application extends ConsumerWidget { const Application({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp( theme: defaultTheme, localizationsDelegates: Resource.localizationsDelegates, supportedLocales: Resource.supportedLocales, localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) { if (supportedLocales.any((element) => locale?.languageCode.contains(element.toString()) == true)) { ref.watch(localeCodeProvider.notifier).state = locale!.languageCode; return locale; } return const Locale('ko', ''); }, routes: { homePageRoutes: (_) => const HomePage(), detailPageRoutes: (_) => const DetailPage(), }, ); } }
Provider代码
import 'package:hooks_riverpod/hooks_riverpod.dart'; final localeCodeProvider = StateProvider<String>((ref) => 'en');
报错信息
E/flutter ( 3747): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Tried to modify a provider while the widget tree was building.
E/flutter ( 3747): If you are encountering this error, chances are you tried to modify a provider
E/flutter ( 3747): in a widget life-cycle, such as but not limited to:
E/flutter ( 3747): - build
E/flutter ( 3747): - initState
E/flutter ( 3747): - dispose
E/flutter ( 3747): - didUpdateWidget
E/flutter ( 3747): - didChangeDepedencies
E/flutter ( 3747):
E/flutter ( 3747): Modifying a provider inside those life-cycles is not allowed, as it could
E/flutter ( 3747): lead to an inconsistent UI state. For example, two widgets could listen to the
E/flutter ( 3747): same provider, but incorrectly receive different states.
E/flutter ( 3747):
E/flutter ( 3747):
E/flutter ( 3747): To fix this problem, you have one of two solutions:
E/flutter ( 3747): - (preferred) Move the logic for modifying your provider outside of a widget
E/flutter ( 3747): life-cycle. For example, maybe you could update your provider inside a button's
E/flutter ( 3747): onPressed instead.
E/flutter ( 3747):
E/flutter ( 3747): - Delay your modification, such as by encasuplating the modification
E/flutter ( 3747): in aFuture(() {...}).
E/flutter ( 3747): This will perform your upddate after the widget tree is done building.
解决方案
报错原因是localeResolutionCallback在Widget树构建阶段执行,此时直接修改Provider状态会触发Riverpod的安全校验。针对必须使用localeResolutionCallback的场景,有两种可靠的修改方式:
方式一:使用Future.microtask延迟更新状态
将修改Provider状态的逻辑包裹在Future.microtask中,确保操作在当前构建周期结束后执行:
localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) { Locale resolvedLocale; if (supportedLocales.any((element) => locale?.languageCode == element.languageCode)) { resolvedLocale = locale!; // 延迟更新Provider状态 Future.microtask(() { ref.read(localeCodeProvider.notifier).state = resolvedLocale.languageCode; }); } else { resolvedLocale = const Locale('ko', ''); Future.microtask(() { ref.read(localeCodeProvider.notifier).state = 'ko'; }); } return resolvedLocale; },
注意:这里使用ref.read而非ref.watch,因为我们只是执行修改操作,不需要监听状态变化。
方式二:使用SchedulerBinding的addPostFrameCallback
通过Flutter的调度器,在Widget树绘制完成后执行状态更新:
import 'package:flutter/scheduler.dart'; // ... localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) { Locale resolvedLocale; if (supportedLocales.any((element) => locale?.languageCode == element.languageCode)) { resolvedLocale = locale!; SchedulerBinding.instance.addPostFrameCallback((_) { ref.read(localeCodeProvider.notifier).state = resolvedLocale.languageCode; }); } else { resolvedLocale = const Locale('ko', ''); SchedulerBinding.instance.addPostFrameCallback((_) { ref.read(localeCodeProvider.notifier).state = 'ko'; }); } return resolvedLocale; },
额外优化:修正语言匹配逻辑
原代码中的locale?.languageCode.contains(element.toString())存在逻辑问题,element.toString()会返回类似ko_KR的字符串,而languageCode是ko,直接对比languageCode更准确:
supportedLocales.any((element) => locale?.languageCode == element.languageCode)
内容的提问来源于stack exchange,提问作者Jun

