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

基于Riverpod Async Notifier Provider实现启动时加载本地存储语言

问题描述

我已在应用中实现本地化功能,支持用户选择语言并保存到本地存储。需求如下:

  • 应用启动时从本地存储加载语言,传递给MaterialApp.router设置首选Locale
  • 使用Riverpod Async Notifier Provider存储语言选择,实现切换语言时自动刷新应用

当前实现虽能运行,但存在不合理之处:采用GoRouter的URL路由,尝试参考教程做初始化时,因在App Widget中监听currentLanguageProvider,导致该Provider在获取值前就被访问,无法使用子Widget方案。


当前代码实现

app.dart

class App extends ConsumerWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final goRouter = ref.watch(goRouterProvider);
    final language = ref.watch(currentLanguageProvider).asData?.value;
    return MaterialApp.router(
      routerConfig: goRouter,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales, 
      locale: language != null ? Locale(language.code) : null,
    );
  }
}

current_language_provider.dart

@Riverpod(keepAlive: true)
class CurrentLanguage extends _$CurrentLanguage {
  Future<LanguageEnum> _getLanguage() async {
    return await ref.read(getLanguageProvider.future);
  }

  @override
  FutureOr<LanguageEnum> build() async {
    return await _getLanguage();
  }

  void set(LanguageEnum value) {
    state = AsyncValue.data(value);
  }
}

language_repository.dart

class LanguageRepository {
  final Ref ref;

  LanguageRepository(this.ref);

  final storage = const FlutterSecureStorage();

  /// 存储语言到本地安全存储
  Future<void> setLanguage(LanguageEnum language) async {
    await storage.write(key: 'language', value: language.code);
    ref.read(currentLanguageProvider.notifier).set(language);
  }

  /// 从本地安全存储获取语言
  Future<LanguageEnum> getLanguage() async {
    final code = await storage.read(key: 'language');
    // 未设置语言时,返回默认语言并存储
    if (code == null) {
      const defaultLanguage = LanguageEnum.eng;
      await setLanguage(defaultLanguage);
      return defaultLanguage;
    }
    for (var value in LanguageEnum.values) {
      if (value.code == code) {
        ref.read(currentLanguageProvider.notifier).set(value);
        return value;
      }
    }
    return LanguageEnum.eng;
  }
}

// LanguageRepository的Riverpod Provider
@Riverpod(keepAlive: true)
LanguageRepository languageRepository(LanguageRepositoryRef ref) {
  return LanguageRepository(ref);
}

@Riverpod(keepAlive: true)
Future<LanguageEnum> getLanguage(GetLanguageRef ref) {
  return ref.read(languageRepositoryProvider).getLanguage();
}

side_drawer.dart

class SideDrawer extends StatelessWidget {
  const SideDrawer({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Drawer(
        child: Column(
          children: [
            Consumer(builder: (BuildContext context, WidgetRef ref, Widget? child) {
              final language = ref.watch(currentLanguageProvider);
              return AsyncValueWidget(
                value: language,
                data: (currentLanguage) => DropdownButton(
                  value: currentLanguage,
                  items: [
                    for (var eachLanguage in LanguageEnum.values)
                      DropdownMenuItem(
                        value: eachLanguage,
                        child: Text(eachLanguage.name),
                      ),
                  ],
                  onChanged: (value) async {
                    if (value != null) {
                      await ref.read(localizationControllerProvider.notifier).setLanguage(value);
                    }
                  },
                ),
              );
            }),
          ],
        ),
      ),
    );
  }
}

localization_controller.dart

@riverpod
class LocalizationController extends _$LocalizationController {
  @override
  FutureOr<void> build() async {
    return;
  }

  Future<void> setLanguage(LanguageEnum language) async {
    final languageRepo = ref.read(languageRepositoryProvider);
    await languageRepo.setLanguage(language);
  }
}

优化方案

1. 重构CurrentLanguage Provider,消除冗余依赖

移除中间的getLanguageProvider,直接在CurrentLanguage的build方法中调用Repository获取语言,避免循环依赖和提前访问问题:

@Riverpod(keepAlive: true)
class CurrentLanguage extends _$CurrentLanguage {
  @override
  FutureOr<LanguageEnum> build() async {
    // 直接从Repository获取初始语言
    return await ref.read(languageRepositoryProvider).getLanguage();
  }

  Future<void> set(LanguageEnum value) async {
    // 先更新存储,再更新状态
    await ref.read(languageRepositoryProvider).setLanguage(value);
    state = AsyncValue.data(value);
  }
}

2. 调整LanguageRepository,移除不必要的Provider调用

去掉getLanguage和setLanguage中对currentLanguageProvider的直接调用,把状态更新的职责交给CurrentLanguage Notifier:

class LanguageRepository {
  final Ref ref;

  LanguageRepository(this.ref);

  final storage = const FlutterSecureStorage();

  Future<void> setLanguage(LanguageEnum language) async {
    await storage.write(key: 'language', value: language.code);
    // 移除这里的set调用,交给CurrentLanguage处理
  }

  Future<LanguageEnum> getLanguage() async {
    final code = await storage.read(key: 'language');
    if (code == null) {
      const defaultLanguage = LanguageEnum.eng;
      await setLanguage(defaultLanguage);
      return defaultLanguage;
    }
    for (var value in LanguageEnum.values) {
      if (value.code == code) {
        return value;
      }
    }
    return LanguageEnum.eng;
  }
}

// 删除冗余的getLanguageProvider

3. 修改App Widget,正确处理AsyncValue状态

在App Widget中使用AsyncValue.when处理加载、错误、成功状态,确保语言加载完成后再渲染MaterialApp.router,避免locale为null的情况:

class App extends ConsumerWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final goRouter = ref.watch(goRouterProvider);
    final languageState = ref.watch(currentLanguageProvider);

    return languageState.when(
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (err, stack) => const Center(child: Text('加载语言失败')),
      data: (language) {
        return MaterialApp.router(
          routerConfig: goRouter,
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: Locale(language.code),
        );
      },
    );
  }
}

4. 优化语言切换逻辑

调整LocalizationController,直接调用CurrentLanguage Notifier的set方法,简化流程:

@riverpod
class LocalizationController extends _$LocalizationController {
  @override
  FutureOr<void> build() async {}

  Future<void> setLanguage(LanguageEnum language) async {
    await ref.read(currentLanguageProvider.notifier).set(language);
  }
}

内容的提问来源于stack exchange,提问作者spehj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 21:34:52