使用Riverpod持久化ThemeMode时启动出现类型不匹配报错
错误根源
重启App时读取shared_preferences是异步操作,但你直接将未完成的Future赋值给了需要同步ThemeMode的参数(如MaterialApp.themeMode),或是Provider定义错误,返回了Future<dynamic>而非ThemeMode,导致类型不匹配报错。
解决方法
方法1:使用Riverpod 2.x的AsyncNotifier(推荐)
这种方式天然支持异步状态管理,能处理加载、错误、数据三种状态:
- 定义
AsyncNotifier和对应Provider:
class ThemeModeNotifier extends AsyncNotifier<ThemeMode> { @override Future<ThemeMode> build() async { // 初始化时读取本地存储的主题模式 final repo = ref.read(settingsRepositoryProvider); final savedMode = await repo.getThemeMode(); // 无保存值时返回系统默认 return savedMode ?? ThemeMode.system; } // 更新并持久化主题模式 Future<void> updateTheme(ThemeMode newMode) async { final repo = ref.read(settingsRepositoryProvider); await repo.saveThemeMode(newMode); state = AsyncData(newMode); } } final themeModeProvider = AsyncNotifierProvider<ThemeModeNotifier, ThemeMode>(ThemeModeNotifier.new);
- 在
MaterialApp中处理异步状态:
class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final themeState = ref.watch(themeModeProvider); return themeState.when( loading: () => const MaterialApp( home: Scaffold(body: Center(child: CircularProgressIndicator())) ), error: (err, _) => MaterialApp( home: Scaffold(body: Center(child: Text('主题加载失败: $err'))) ), data: (themeMode) => MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: themeMode, home: const HomePage(), ), ); } }
方法2:使用StateNotifier+默认初始值
如果坚持用StateNotifier,确保初始状态是合法的ThemeMode,异步加载完成后再更新状态:
- 定义
StateNotifier:
class ThemeNotifier extends StateNotifier<ThemeMode> { final SettingsRepository _repo; ThemeNotifier(this._repo) : super(ThemeMode.system) { // 初始化时异步加载主题 _loadSavedTheme(); } Future<void> _loadSavedTheme() async { final savedMode = await _repo.getThemeMode(); if (savedMode != null) state = savedMode; } Future<void> setThemeMode(ThemeMode mode) async { await _repo.saveThemeMode(mode); state = mode; } } final themeNotifierProvider = StateNotifierProvider<ThemeNotifier, ThemeMode>((ref) { final repo = ref.read(settingsRepositoryProvider); return ThemeNotifier(repo); });
- 确保
SettingsRepository方法返回正确类型:
class SettingsRepository { final SharedPreferences _prefs; SettingsRepository(this._prefs); Future<ThemeMode?> getThemeMode() async { final modeStr = _prefs.getString('theme_mode'); if (modeStr == null) return null; // 转换为ThemeMode枚举,避免返回dynamic return ThemeMode.values.firstWhere((mode) => mode.name == modeStr); } Future<void> saveThemeMode(ThemeMode mode) async { await _prefs.setString('theme_mode', mode.name); } } // 封装SharedPreferences的Provider final sharedPreferencesProvider = FutureProvider<SharedPreferences>((ref) async { return await SharedPreferences.getInstance(); }); // 确保SettingsRepository依赖的SharedPreferences已初始化 final settingsRepositoryProvider = FutureProvider<SettingsRepository>((ref) async { final prefs = await ref.watch(sharedPreferencesProvider.future); return SettingsRepository(prefs); });
- 在
MaterialApp中使用:
class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // 等待SettingsRepository初始化完成 final repoAsync = ref.watch(settingsRepositoryProvider); return repoAsync.when( loading: () => const MaterialApp(home: Scaffold(body: Center(child: CircularProgressIndicator()))), error: (err, _) => MaterialApp(home: Scaffold(body: Center(child: Text('初始化失败: $err')))), data: (_) { final themeMode = ref.watch(themeNotifierProvider); return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: themeMode, home: const HomePage(), ); }, ); } }
关键注意点
- 禁止直接将
Future类型赋值给需要ThemeMode的参数,必须等待异步操作完成后再使用结果。 - 确保
SettingsRepository的getThemeMode方法返回Future<ThemeMode?>而非Future<dynamic>,避免类型转换错误。 - 使用Riverpod的异步状态组件(如
AsyncNotifier、FutureProvider)处理异步依赖的初始化流程。
内容的提问来源于stack exchange,提问作者Ydev
相关产品推荐
相关产品推荐

