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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 21:03:58