在实例化时使用Riverpod StateNotificationer异步加载SharedPreferences时的竞争



小工具:

HomePageView
-- GuessTheWordPage 

我有一个跟踪统计数据的提供商。StateNotificationer实例化时,会从SharedPreferences加载保存的统计信息。问题是,当它第一次使用时,它调用incrementStarts((,但_load((还没有完成,所以它在加载保存的数据之前会递增。

我可以通过在HomePageView而不是GameView中查看提供商来解决这个问题。但它感觉很脏,因为从来没有在那里使用过。

如何确保数据在使用前已从SharedPreferences加载?我曾尝试更改它,使_load((从另一个函数_init((async{wait_load((;}调用,但它并不能解决问题。

正如我所说,我可以将HomeView更改为ConsumerWidget,并添加:StatsState StatsState=ref.watch(statsProvider(;但感觉很脏。

logs
I/flutter (11187): StatsStateNotifier::load()
I/flutter (11187): StatsStateNotifier::incrementStarts()
I/flutter (11187): starts: 0
I/flutter (11187): starts: 1
I/flutter (11187): json: {"wins":0,"loses":0,"starts":4,"tried":0,"guesses":[]}
I/flutter (11187): StatsStateNotifier::load() -- done
class GuessTheWordStats {
int wins; // won a game
int loses; // lost a game, did not find the solution
int starts; // started a new game
int tried; // tried at least one solution
List<int> guesses; // number of guesses to get solution
GuessTheWordStats({
required this.wins,
required this.loses,
required this.starts,
required this.tried,
required this.guesses,
});
factory GuessTheWordStats.fromJson(Map<String, dynamic> json) {
return GuessTheWordStats(
wins: json['wins'],
loses: json['loses'],
starts: json['starts'],
tried: json['tried'],
guesses: List<int>.from(json['guesses']),
);
}
Map<String, dynamic> toJson() => {
'wins': wins,
'loses': loses,
'starts': starts,
'tried': tried,
'guesses': guesses,
};
static GuessTheWordStats init() {
return GuessTheWordStats(
wins: 0, loses: 0, starts: 0, tried: 0, guesses: []);
}
}
@immutable
class StatsState {
const StatsState({
required this.guessTheWordStats,
});
final GuessTheWordStats guessTheWordStats;
StatsState copyWith({
GuessTheWordStats? guessTheWordStats,
}) {
return StatsState(
guessTheWordStats: guessTheWordStats ?? this.guessTheWordStats,
);
}
}
StatsState settingsInitialState() {
return StatsState(
guessTheWordStats: GuessTheWordStats.init(),
);
}
class StatsStateNotifier extends StateNotifier<StatsState> {
StatsStateNotifier() : super(settingsInitialState()) {
_load();
}
Future<void> _load() async {
print('StatsStateNotifier::load()');
final prefs = await SharedPreferences.getInstance();
final String? json = prefs.getString('GuessTheWordStats');
print('json: $json');
GuessTheWordStats guessTheWordStats = GuessTheWordStats.init();
if (json != null) {
guessTheWordStats = GuessTheWordStats.fromJson(jsonDecode(json));
}
state = state.copyWith(
guessTheWordStats: guessTheWordStats,
);
// print('GuessTheWordStats.wins: ${guessTheWordStats.wins}');
// print('GuessTheWordStats.loses: ${guessTheWordStats.loses}');
// print('GuessTheWordStats.starts: ${guessTheWordStats.starts}');
// print('GuessTheWordStats.tried: ${guessTheWordStats.tried}');
// print('GuessTheWordStats.guesses: ${guessTheWordStats.guesses}');
print('StatsStateNotifier::load() -- done');
}
Future<void> _save() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
'GuessTheWordStats', jsonEncode(state.guessTheWordStats.toJson()));
}
incrementWins() {
GuessTheWordStats guessTheWordStats = state.guessTheWordStats;
guessTheWordStats.wins++;
state = state.copyWith(
guessTheWordStats: guessTheWordStats,
);
_save();
}
incrementStarts() {
print('StatsStateNotifier::incrementStarts()');
print('starts: ${state.guessTheWordStats.starts}');
GuessTheWordStats guessTheWordStats = state.guessTheWordStats;
guessTheWordStats.starts++;
state = state.copyWith(
guessTheWordStats: guessTheWordStats,
);
_save();
print('starts: ${state.guessTheWordStats.starts}');
}
incrementLoses() {
GuessTheWordStats guessTheWordStats = state.guessTheWordStats;
guessTheWordStats.loses++;
state = state.copyWith(
guessTheWordStats: guessTheWordStats,
);
_save();
}
}
final StateNotifierProvider<StatsStateNotifier, StatsState> statsProvider =
StateNotifierProvider<StatsStateNotifier, StatsState>((ref) {
return StatsStateNotifier();
});
class GuessTheWordPage extends ConsumerStatefulWidget {
const GuessTheWordPage({Key? key}) : super(key: key);
@override
GuessTheWordPageState createState() => GuessTheWordPageState();
}
class GuessTheWordPageState extends ConsumerState<GuessTheWordPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
MainAppSettings mainAppSettings = MainAppSettings();
_initSettings() async {
await mainAppSettings.loadFromPrefs();
}
_initNewGame() async {
await ref.read(guessTheWordProvider.notifier).newGame();
// first use of statsProvider //
ref.read(statsProvider.notifier).incrementStarts();
// -- //
}
@override
void initState() {
if (kDebugMode) print('GuessTheWordPage::initState()');
super.initState();
_initSettings();
_initNewGame();
}
@override
Widget build(BuildContext context) {

这是一件很常见的事情,另一个例子是希望在第一次尝试从数据库中查询之前对数据库进行初始化。

Riverpod使得使用提供程序变得非常容易。我不确定这是否是最好的做法,但我已经做了很多,从来没有遇到过任何问题。首先要做的是为未实现的共享首选项创建一个提供程序。

类似以下内容。

/// If you're using Riverpod annotations
@riverpod
SharedPreferences sharedPreferences(SharedPreferencesRef ref) =>
throw UnimplementedError();
/// If you're using vanilla Riverpod
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
throw UnimplementedError();
});

然后在你的主文件中,你覆盖它作为你的提供者覆盖的一部分,就像这样

void main() async {
WidgetsFlutterBinding.ensureInitialized();
final SharedPreferences prefs = await SharedPreferences.getInstance();
runApp(ProviderScope(
overrides: [
sharedPreferencesProvider.overrideWithValue(prefs),
],
child: const MyApp(),
));
}

现在,在你的应用程序中任何你想使用共享偏好的地方,只要阅读共享偏好提供程序,它就已经加载了。

ref.read(sharedPreferencesProvider).setString('pref', value);

这样做的好处是,所有pref的加载都是提前完成的,并且是应用程序启动的一部分(可以说这是用户满足于等待的一次(。

最新更新