Flutter如何从SharedPreferences获取并应用已保存的语言Locale配置
问题根源
你现有代码存在5个核心问题导致重启后语言配置不生效:
- 存储语言编码时取值错误:你存的是
countryCode(国家码),但en/uk/ru属于languageCode(语言码),纯语言构造的Locale对象countryCode默认是null,导致你每次切换语言实际存到本地的都是兜底值"en" - 应用启动时从未调用你写的
getLocaleFromSettings()方法,Provider初始化时直接写死了默认英文配置 getLocaleFromSettings()读取完本地配置后没有调用notifyListeners(),就算值更新了UI也不会响应MaterialApp的locale参数被你注释掉了,Provider的locale值根本没有绑定到应用配置上- 切换语言时
setLocale和changeLocaleSettings逻辑重复,冗余代码容易引发状态不一致
修复步骤
1. 重构LocaleProvider代码
把重复逻辑合并,修正存储字段,补全初始化读取后的通知逻辑:
import 'package:flutter/material.dart'; import 'package:myapp/l10n/l10n.dart'; import 'package:shared_preferences/shared_preferences.dart'; class LocaleProvider extends ChangeNotifier { Locale? _locale; // 初始化加载状态,避免启动时闪默认语言 bool _isInitialized = false; Locale? get locale => _locale; bool get isInitialized => _isInitialized; // 初始化方法,应用启动时调用 Future<void> loadSavedLocale() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final String savedLangCode = prefs.getString("lang_code") ?? "en"; // 校验存储的语言是否在支持列表里 _locale = L10n.all.firstWhere( (element) => element.languageCode == savedLangCode, orElse: () => const Locale('en'), ); _isInitialized = true; notifyListeners(); } // 切换语言方法,合并原有两个方法的逻辑 Future<void> setLocale(Locale locale) async { if (!L10n.all.contains(locale)) return; _locale = locale; notifyListeners(); // 持久化存储,注意存的是languageCode final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString("lang_code", locale.languageCode); } void clearLocale() async { _locale = const Locale('en'); final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove("lang_code"); notifyListeners(); } }
注意:原来你用的存储key是"code",这里改成了语义更明确的"lang_code",如果要兼容之前的存储可以替换回原来的key,但是一定要确保存的是languageCode字段
2. 修改main.dart入口逻辑
在应用启动时先调用加载本地配置的方法,同时把locale参数绑定到MaterialApp上:
void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => LocaleProvider()..loadSavedLocale(), builder: (context, child) { final provider = Provider.of<LocaleProvider>(context); // 等待本地配置加载完成再渲染主页面,避免启动时闪默认语言 if (!provider.isInitialized) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center(child: CircularProgressIndicator()), ), ); } return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( scaffoldBackgroundColor: Colors.lightGreen[100], primarySwatch: Colors.green, ), // 这里一定要打开,绑定provider的locale配置 locale: provider.locale, supportedLocales: L10n.all, localizationsDelegates: [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], home: const HomePage(), ); }, ); } }
如果觉得启动时的加载圈多余,也可以去掉加载判断,直接给locale一个默认值const Locale('en'),因为SharedPreferences读取速度极快,几乎不会有可感知的闪烁。
3. 简化语言选择组件逻辑
去掉重复的方法调用,直接调用重构后的setLocale即可:
class LanguagePickerWidget extends StatelessWidget { const LanguagePickerWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final provider = Provider.of<LocaleProvider>(context); final currentLocale = provider.locale ?? const Locale('en'); return DropdownButtonHideUnderline( child: SizedBox( width: 15, child: Theme( data: Theme.of(context).copyWith( canvasColor: Colors.green.shade300, ), child: DropdownButton( borderRadius:BorderRadius.circular(12), isExpanded: true, itemHeight: null, value: currentLocale, items: L10n.all.map( (locale) { final flag = L10n.getFlag(locale.languageCode); return DropdownMenuItem( child: Align( alignment: Alignment.center, child: Text( flag, style: const TextStyle(fontSize: 22.0), ), ), value: locale, ); }, ).toList(), onChanged: (newLocale) { if (newLocale == null) return; // 直接调用统一的setLocale方法即可,不用再调用其他方法 Provider.of<LocaleProvider>(context, listen: false).setLocale(newLocale); }, ), ), ), ); } }
这里把原来写在DropdownMenuItem的onTap逻辑移到了DropdownButton的onChanged回调里,符合组件的设计规范,避免点按区域异常的问题
验证要点
改完之后可以测试:切换语言→杀掉应用进程→重新打开,应用会直接展示你上次选择的语言,不会回退到英文。
内容的提问来源于stack exchange,提问作者Vadym Silchenko
相关产品推荐
相关产品推荐

