Я пытаюсь сохранить ThemeMode, используя пакеты Riverpod и shared_preferences
.
Вот что я сделал в themeNotifier
:
import 'package:flutter/material.dart';
import 'settings_repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'theme_notifier.g.dart';
@riverpod
class ThemeNotifier extends _$ThemeNotifier {
@override
ThemeMode build() {
return ThemeMode.system;
}
Future themeMode() async {
state = await ref.watch(settingsRepositoryProvider).loadThemeMode();
}
Future updateThemeMode(ThemeMode? newThemeMode) async {
if (newThemeMode == null) return;
if (state == newThemeMode) return;
state = newThemeMode;
await ref
.watch(settingsRepositoryProvider)
.updateThemeMode(theme: newThemeMode);
}
}
и вот что я сделал в settingsRepository
:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'settings_repository.g.dart';
class SettingsRepository {
final Future<SharedPreferencesWithCache> _prefs =
SharedPreferencesWithCache.create(
cacheOptions: const SharedPreferencesWithCacheOptions(
// This cache will only accept the key 'isDarkMode'.
allowList: <String>{'isDarkMode'}));
SettingsRepository();
get prefs => _prefs;
Future<ThemeMode> loadThemeMode() async {
final SharedPreferencesWithCache prefs = await _prefs;
final isDarkMode = prefs.getBool('isDarkMode');
log('THEME MODE :: $isDarkMode');
return isDarkMode == null
? ThemeMode.system
: isDarkMode
? ThemeMode.dark
: ThemeMode.light;
}
Future<void> updateThemeMode({ThemeMode? theme}) async {
final SharedPreferencesWithCache prefs = await _prefs;
await prefs.setBool('isDarkMode', theme == ThemeMode.dark);
log('THEME MODE UPDATE:: $theme');
log('isDarkMode :: ${prefs.getBool('isDarkMode')}');
}
}
@riverpod
SettingsRepository settingsRepository(SettingsRepositoryRef ref) =>
SettingsRepository();
затем я потребляю свой materialapp
и передаю его провайдеру:
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, _) {
return MaterialApp(
home: const RootNavigationBar(),
restorationScopeId: 'app',
debugShowCheckedModeBanner: false,
theme: ThemeData(),
darkTheme: ThemeData.dark(),
themeMode: ref.watch(themeNotifierProvider),
},
);
}
Все работает нормально, но когда я пытаюсь закрыть приложение, а затем открыть его, выдает эту ошибку.
════════ Exception caught by widgets library ═══════════════════════════════════
The following _TypeError was thrown building Consumer(dirty, dependencies: [UncontrolledProviderScope], state: _ConsumerState#733d8):
type 'Future<dynamic>' is not a subtype of type 'ThemeMode'
The relevant error-causing widget was:
Consumer Consumer:file:///D:/Documents/Flutter/myapp/lib/src/app.dart:27:12
Затем приложение запускается нормально после нескольких секунд темного экрана.
@DanR Чего следует избегать, так это использования ref.watch
после асинхронного разрыва. В коде ОП его нет. Проблема заключается в использовании ref.watch
в коде, который выполняется императивно. В этом случае вместо него следует использовать ref.read
.
Я согласен. Точнее, в документации говорится: «Метод просмотра не следует вызывать асинхронно, как внутри onPressed ElevatedButton». см. riverpod.dev/docs/concepts/… .
Наконец я нашел решение: проблема была в themeNotifier при использовании метода ref.watch для доступа к провайдеру. а затем вызвать функцию themeMode внутри метода сборки, а не возвращать ее из него, и на самом деле это была единственная проблема (вернуть асинхронную функцию внутри метода сборки themeNotifier)
Комментарий @Dan R был полезен, он предложил избегать ref.watch в асинхронном контексте. Это хорошая практика.
вот окончательное решение кода:
import 'package:flutter/material.dart';
import 'settings_repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'theme_notifier.g.dart';
@riverpod
class ThemeNotifier extends _$ThemeNotifier {
@override
ThemeMode build() {
themeMode();
return ThemeMode.system;
}
themeMode() async {
final ThemeMode themeMode =
await ref.read(settingsRepositoryProvider).loadThemeMode();
state = themeMode;
}
Future updateThemeMode(ThemeMode? newThemeMode) async {
if (newThemeMode == null) return;
if (state == newThemeMode) return;
state = newThemeMode;
await ref
.read(settingsRepositoryProvider)
.updateThemeMode(theme: newThemeMode);
}
}
В документации Riverpod рекомендуется избегать использования
ref.watch
в асинхронном контексте.