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
相关产品推荐
相关产品推荐

