You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 16:25:28