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

Flutter中MaterialApp.router使用Riverpod StateNotifier切换主题不重建问题

问题:Flutter中使用Riverpod StateNotifier切换主题时MaterialApp.router未自动重建

点击颜色按钮切换主题后,界面没有即时更新,只有手动热重载才能看到主题变化。

使用的包版本:

dependencies:
  go_router: ^9.0.0
  flutter_riverpod: ^2.3.6
  riverpod_annotation: ^2.1.1
dev_dependencies:
  build_runner: ^2.4.6
  riverpod_generator: ^2.2.3

相关代码:
main.dart

void main() {
  runApp(
    ProviderScope(
      child: const MyApp(),
    ),
  );
}

app.dart

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp.router(
      title: "MyApp",
      routerConfig: goRouter,
      locale: Locale("en"),
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ref.watch(themeProvider).state,
    );
  }
}

theme_provider.dart

part 'theme_provider.g.dart';

@riverpod
ThemeState theme(ThemeRef ref) {
  return ThemeState(themeRepository: ThemeRepository());
}

class ThemeState extends StateNotifier<ThemeData> {
  IThemeRepository themeRepository;

  ThemeState({required IThemeRepository this.themeRepository})
      : super(AppTheme.gold.getThemeData) {
    _getStoredTheme();
  }

  void setCurrentTheme(AppTheme value) {
    state = value.getThemeData;
    _storeTheme(value);
  }

  Future<void> _getStoredTheme() async {
    String themeName = await themeRepository.GetTheme();
    state = AppTheme.values.byName(themeName).getThemeData;
  }

  Future<bool> _storeTheme(AppTheme appTheme) async {
    return await themeRepository.SetTheme(appTheme.name);
  }
}

app_theme.dart

enum AppTheme {
  gold,
  green,
  red,
  black;

  ThemeData get getThemeData => switch (this) {
        AppTheme.gold => Themes.goldTheme,
        AppTheme.green => Themes.greenTheme,
        AppTheme.red => Themes.redTheme,
        AppTheme.black => Themes.blackTheme,
      };
}

themes.dart

class Themes {
  static ThemeData get baseTheme => ThemeData(
        useMaterial3: true,
        brightness: Brightness.light,
      );
  static ThemeData get goldTheme => baseTheme.copyWith(
        primaryColor: Color(0xffB58A35),
        colorScheme: ColorScheme.fromSeed(seedColor: Color(0xffB58A35)),
      );
  static ThemeData get greenTheme => baseTheme.copyWith(
        primaryColor: Color(0xff347743),
        colorScheme: ColorScheme.fromSeed(seedColor: Color(0xff347743)),
      );
  static ThemeData get redTheme => baseTheme.copyWith(
        primaryColor: Color(0xffD83731),
        colorScheme: ColorScheme.fromSeed(seedColor: Color(0xffD83731)),
      );
  static ThemeData get blackTheme => baseTheme.copyWith(
        primaryColor: Color(0xff313438),
        colorScheme: ColorScheme.highContrastDark(
          primary: Color(0xff313438),
          secondary: Colors.white,
        ),
      );
}

settings_page.dart

Row(
  mainAxisAlignment:
  MainAxisAlignment.spaceEvenly,
  children: [
    GestureDetector(
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: AppTheme
              .gold.getThemeData.primaryColor,
          borderRadius:
          BorderRadius.circular(4),
        ),
      ),
      onTap: () {
        ref
            .read(themeProvider)
            .setCurrentTheme(AppTheme.gold);
      },
    ),
    SizedBox(width: 4),
    GestureDetector(
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: AppTheme
              .green.getThemeData.primaryColor,
          borderRadius:
          BorderRadius.circular(4),
        ),
      ),
      onTap: () {
        ref
            .read(themeProvider)
            .setCurrentTheme(AppTheme.green);
      },
    ),
    SizedBox(width: 4),
    GestureDetector(
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: AppTheme
              .red.getThemeData.primaryColor,
          borderRadius:
          BorderRadius.circular(4),
        ),
      ),
      onTap: () {
        ref
            .read(themeProvider)
            .setCurrentTheme(AppTheme.red);
      },
    ),
    SizedBox(width: 4),
    GestureDetector(
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: AppTheme
              .black.getThemeData.primaryColor,
          borderRadius:
          BorderRadius.circular(4),
        ),
      ),
      onTap: () {
        ref
            .read(themeProvider)
            .setCurrentTheme(AppTheme.black);
      },
    ),
  ],
);

解决方案

问题根源

当前的themeProvider是普通Provider<ThemeState>,当ThemeState内部的state(即ThemeData)变化时,Provider不会发出更新通知——因为Provider持有的ThemeState实例本身没有被替换,所以ref.watch(themeProvider)无法触发MyApp组件重建。

修复方法(二选一)

方法一:修改app.dart中的监听逻辑

通过select方法直接监听ThemeState内部的state变化,当ThemeData更新时触发组件重建:

修改app.dart的build方法:

@override
Widget build(BuildContext context, WidgetRef ref) {
  // 监听ThemeState的state属性变化
  final themeData = ref.watch(themeProvider.select((themeState) => themeState.state));
  
  return MaterialApp.router(
    title: "MyApp",
    routerConfig: goRouter,
    locale: Locale("en"),
    localizationsDelegates: AppLocalizations.localizationsDelegates,
    supportedLocales: AppLocalizations.supportedLocales,
    theme: themeData,
  );
}

方法二:重构theme_provider.dart为标准StateNotifierProvider(推荐)

使用Riverpod Generator的标准写法生成StateNotifierProvider,它会自动监听StateNotifier的state变化:

修改theme_provider.dart:

part 'theme_provider.g.dart';

@riverpod
class ThemeState extends _$ThemeState {
  late IThemeRepository _themeRepository;

  @override
  ThemeData build() {
    _themeRepository = ThemeRepository();
    // 初始化时加载存储的主题
    _loadStoredTheme();
    return AppTheme.gold.getThemeData;
  }

  void setCurrentTheme(AppTheme value) {
    state = value.getThemeData;
    _storeTheme(value);
  }

  Future<void> _loadStoredTheme() async {
    String themeName = await _themeRepository.GetTheme();
    state = AppTheme.values.byName(themeName).getThemeData;
  }

  Future<bool> _storeTheme(AppTheme appTheme) async {
    return await _themeRepository.SetTheme(appTheme.name);
  }
}

然后修改app.dart的监听方式:

@override
Widget build(BuildContext context, WidgetRef ref) {
  // 直接watch themeStateProvider,自动监听state变化
  final themeData = ref.watch(themeStateProvider);
  
  return MaterialApp.router(
    title: "MyApp",
    routerConfig: goRouter,
    locale: Locale("en"),
    localizationsDelegates: AppLocalizations.localizationsDelegates,
    supportedLocales: AppLocalizations.supportedLocales,
    theme: themeData,
  );
}

重构后需重新运行build_runner生成代码:

flutter pub run build_runner build

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 06:17:04