Flutter基于Flux Store应用切换语言不生效问题求助
问题:Flutter应用切换语言后配置文件内容无法自动更新,重启逻辑失效
我开发了一个基于Flux Store架构的Flutter应用,切换语言时,config_ar这类配置文件中的内容(如商品名称、横幅文案等)无法自动切换为config_en,必须完全重启应用才会生效。我尝试在捕获语言切换事件时调用应用重启逻辑,但并未成功,相关代码如下:
main.dart
import 'dart:io' show HttpClient, SecurityContext; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart' as foundation; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_platform/universal_platform.dart'; import 'app.dart'; import 'common/config.dart'; import 'common/constants.dart'; import 'common/tools.dart'; import 'env.dart'; import 'modules/webview/index.dart'; import 'services/dependency_injection.dart'; import 'services/locale_service.dart'; import 'services/services.dart'; Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); printLog('Handling a background message ${message.messageId}'); } void main() { printLog('[main] ===== START main.dart ======='); WidgetsFlutterBinding.ensureInitialized(); Configurations().setConfigurationValues(environment); /// Fix issue android sdk version 22 can not run the app. if (UniversalPlatform.isAndroid) { SecurityContext.defaultContext .setTrustedCertificatesBytes(Uint8List.fromList(isrgRootX1.codeUnits)); } runApp( RestartWidget( child: const MaterialApp(), ), ); /// Support Webview (iframe) for Flutter Web. Requires this below header. /// Content-Security-Policy: frame-ancestors 'self' *.yourdomain.com registerWebViewWebImplementation(); /// Update status bar color on Android if (isMobile) { SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( statusBarColor: Colors.transparent, systemNavigationBarColor: Colors.black, )); } Provider.debugCheckInvalidValueType = null; var languageCode = kAdvanceConfig.defaultLanguage; LicenseRegistry.addLicense(() async* { final license = await rootBundle.loadString('google_fonts/OFL.txt'); yield LicenseEntryWithLineBreaks(['google_fonts'], license); }); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); runZonedGuarded(() async { if (!foundation.kIsWeb) { /// Enable network traffic logging. HttpClient.enableTimelineLogging = !foundation.kReleaseMode; /// Lock portrait mode. unawaited(SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp])); } await GmsCheck().checkGmsAvailability(enableLog: foundation.kDebugMode); try { if (isMobile) { /// Init Firebase settings due to version 0.5.0+ requires to. /// Use await to prevent any usage until the initialization is completed. await Services().firebase.init(); await Configurations().loadRemoteConfig(); } } catch (e) { printLog(e); printLog('🔴 Firebase init issue'); } await DependencyInjection.inject(); Services().setAppConfig(serverConfig); if (isMobile && kAdvanceConfig.autoDetectLanguage) { final language1 = injector<SharedPreferences>().getString('language'); if (language1?.isEmpty ?? true) { languageCode = await LocaleService().getDeviceLanguage(); } else { languageCode = language1.toString(); } } if (serverConfig['type'] == 'vendorAdmin') { return runApp(Services() .getVendorAdminApp(languageCode: languageCode, isFromMV: false)); } if (serverConfig['type'] == 'delivery') { return runApp(Services() .getDeliveryApp(languageCode: languageCode, isFromMV: false)); } ResponsiveSizingConfig.instance.setCustomBreakpoints( const ScreenBreakpoints(desktop: 900, tablet: 600, watch: 100)); runApp(App(languageCode: languageCode)); }, printError); } class RestartWidget extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables RestartWidget({required this.child}); final Widget child; static void restartApp(BuildContext context) { context.findAncestorStateOfType<_RestartWidgetState>()?.restartApp(); } @override // ignore: library_private_types_in_public_api _RestartWidgetState createState() => _RestartWidgetState(); } class _RestartWidgetState extends State<RestartWidget> { Key key = UniqueKey(); void restartApp() { setState(() { key = UniqueKey(); }); } @override Widget build(BuildContext context) { return KeyedSubtree( key: key, child: widget.child, ); } }
change_language_mixin.dart
import 'package:flutter/material.dart'; import 'package:inspireui/widgets/circle_button_text.dart'; import 'package:inspireui/widgets/radio_button.dart'; import 'package:provider/provider.dart'; import '../../common/config.dart'; import '../../main.dart'; import '../../models/index.dart'; mixin ChangeLanguage<T extends StatefulWidget> on State<T> { List<Map> get languages => getLanguages(); AppModel get appModel => Provider.of<AppModel>(context, listen: false); void onTapChangeLanguage(Map langInfo) { appModel.changeLanguage(langInfo['code'], context); RestartWidget.restartApp(context); } void _showModel(context) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) { return Material( color: Colors.transparent, child: Container( decoration: const BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15), ), color: Colors.white, ), padding: const EdgeInsets.only(top: 10, bottom: 10, left: 16, right: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Align( alignment: Alignment.centerRight, child: GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(18), color: const Color(0xFF555555).withOpacity(0.1), ), child: const Padding( padding: EdgeInsets.all(3.0), child: Icon(Icons.close), ), ), ), ), const Text( 'Change language', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, ), ), const Padding( padding: EdgeInsets.only(top: 8, bottom: 16), child: Text( 'Which language do you prefer?', style: TextStyle(fontSize: 14), ), ), Expanded( child: ListView.builder( key: const Key('changeLanguageList'), itemCount: languages.length, itemBuilder: (context, i) { return GestureDetector( key: ValueKey('changeLanguageTo${languages[i]['text']}'), onTap: () { onTapChangeLanguage(languages[i]); RestartWidget.restartApp(context); }, child: Container( color: Colors.white, padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Image.asset( languages[i]['icon'], width: 30, height: 20, fit: BoxFit.cover, ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text( languages[i]['text'], style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600), ), ), ), RadioButton( radius: 14, isActive: languages[i]['code'] .toString() .toLowerCase() == appModel.langCode.toLowerCase(), ), ], ), ), ); }, ), ) ], ), ), ); }, ); } Widget iconLanguage() { return Align( alignment: Alignment.topRight, child: SafeArea( child: Padding( padding: const EdgeInsets.only(right: 16.0, top: 16), child: GestureDetector( key: const Key('changeLanguageIconButton'), onTap: () => _showModel(context), child: Selector<AppModel, String>( selector: (_, model) => model.langCode, builder: (context, langCode, child) => CircleButtonText( langCode.toUpperCase(), radius: 16, ), ), ), ), ), ); } }
解决方案
1. 修复RestartWidget的包裹范围
当前代码中,RestartWidget包裹的是一个空的MaterialApp,之后在runZonedGuarded里又重新调用runApp加载实际应用,导致RestartWidget并未真正包裹住应用根组件,调用重启方法时找不到对应状态。
修改main.dart,将RestartWidget移到runZonedGuarded内的runApp中:
// 移除开头的空MaterialApp runApp代码 // runApp( // RestartWidget( // child: const MaterialApp(), // ), // ); // ... 其他代码保持不变 ... // 在runZonedGuarded末尾,用RestartWidget包裹应用 if (serverConfig['type'] == 'vendorAdmin') { return runApp( RestartWidget( child: Services().getVendorAdminApp(languageCode: languageCode, isFromMV: false), ), ); } if (serverConfig['type'] == 'delivery') { return runApp( RestartWidget( child: Services().getDeliveryApp(languageCode: languageCode, isFromMV: false), ), ); } ResponsiveSizingConfig.instance.setCustomBreakpoints( const ScreenBreakpoints(desktop: 900, tablet: 600, watch: 100)); runApp( RestartWidget( child: App(languageCode: languageCode), ), );
2. 优化RestartWidget,避免依赖Context
将RestartWidget改为使用全局Key,无需传入Context即可调用重启:
class RestartWidget extends StatefulWidget { RestartWidget({required this.child}); final Widget child; static final GlobalKey<_RestartWidgetState> _restartKey = GlobalKey<_RestartWidgetState>(); static void restartApp() { _restartKey.currentState?.restartApp(); } @override _RestartWidgetState createState() => _RestartWidgetState(); } class _RestartWidgetState extends State<RestartWidget> { Key _appKey = UniqueKey(); void restartApp() { setState(() { _appKey = UniqueKey(); }); } @override Widget build(BuildContext context) { return KeyedSubtree( key: RestartWidget._restartKey, child: KeyedSubtree( key: _appKey, child: widget.child, ), ); } }
调用时直接用RestartWidget.restartApp(),无需传Context:
void onTapChangeLanguage(Map langInfo) async { appModel.changeLanguage(langInfo['code'], context); RestartWidget.restartApp(); }
3. 切换语言时重新加载对应配置文件
语言切换后,必须重新加载对应语言的配置文件(如config_en、config_ar),否则即使重启应用,配置还是旧的。
假设Configurations类有loadConfigurationForLanguage方法用于加载指定语言的配置,修改onTapChangeLanguage:
Future<void> onTapChangeLanguage(Map langInfo) async { final newLangCode = langInfo['code']; // 更新AppModel语言 appModel.changeLanguage(newLangCode, context); // 保存语言到本地存储 await injector<SharedPreferences>().setString('language', newLangCode); // 重新加载对应语言的配置 await Configurations().loadConfigurationForLanguage(newLangCode); // 重启应用 RestartWidget.restartApp(); }
同时,在main.dart启动应用前,也要根据当前语言加载对应配置:
// 获取languageCode后添加 await Configurations().loadConfigurationForLanguage(languageCode); // 再启动应用 if (serverConfig['type'] == 'vendorAdmin') { return runApp(...); }
4. 修正语言切换事件的异步调用
由于新增了异步操作,需要将onTapChangeLanguage改为异步方法,并在ListView的点击事件中等待执行:
// 在mixin中修改onTap为异步 onTap: () async { await onTapChangeLanguage(languages[i]); },
内容的提问来源于stack exchange,提问作者Abdalrazak
相关产品推荐
相关产品推荐

