在RiverPod中,如何编写一个异步设置(仅一次)的StateNotifier

Ben*_*Lee 5 flutter riverpod flutter-hive

这里是 Riverpod 的新手。(使用 Flutter 和 hooks_riverpod,顺便说一句)。

使用 Hive 存储相关的项目列表。我需要调用Hive.initFlutter并等待 Hive 初始化,并在我的应用程序加载时执行相同的操作来打开配置单元框。之后,对 Hive 的调用是同步的。

我最初的想法(尽管我愿意接受更好的想法)是创建一个包含两个列表的 StateNotifier。该通知程序可以有一个异步的 setUp 函数。简化后的样子如下:

class ItemsNotifier extends StateNotifier<ItemsState> {
  ItemsNotifier() : super(ItemsState([], []));

  setUp() async {
    await Hive.initFlutter();
    // What is ItemDao? It's a data accessor object singleton used to house Hive logic.
    await ItemDao().openBoxes();
    // Putting a breakpoint here, I can see by calling `ItemDao().list1` etc that the lists have loaded with items as expected, but setting state here does not trigger a rebuild of the consumer widget.
    state.list1 = ItemDao().list1;
    state.list2 = ItemDao().list2;
  }

  ...getters and setters and other functions omitted...
}

final itemsProvider = StateNotifierProvider<ItemsNotifier, ItemsState>((ref) {
  final notifier = ItemsNotifier();
  notifier.setUp(); // I've never seen anything to suggest that calling an async setUp method here is supported, it's just something I tried.
  return notifier;
});

class ItemsState {
  List<Item> list1;
  List<Item> list2;
  ItemsState(this.list1, this.list2);
}
Run Code Online (Sandbox Code Playgroud)

正如评论中提到的,我在构造 itemsProvider 时调用异步 setUp 方法。我在设置方法和消费者小部件中放置了一个断点。首先,小部件内的断点捕获,我们看到它list1是空的,正如预期的那样。接下来,设置方法中的断点将被捕获。我们看到 ItemDao().list1 充满了项目,因此从 Hive 加载成功。所以我预计调用state.list1 =会导致消费者像平常一样重新加载。但事实并非如此。小部件中的断点不会再次捕获,小部件仍为空。可能是因为 Riverpod 不希望异步方法从 StateNotifierProvider 构造函数内部更改状态。

因此,这个问题的一个简单解决方案可能只是回答我应该在应用程序中的哪个位置调用 setUp()?它需要位于应用程序启动时仅运行一次的地方。在某个有状态小部件的 initState 中?这感觉不太对……正如我所说,我是使用 Riverpod 的新手。

或者,如果您有另一种(更好的)方法来构建此方案,那也会有所帮助。

考虑的解决方案:

我会注意到我还尝试使用 Riverpod 的FutureProvider。它适用于加载列表。但是,正如文档中所述,FutureProvider 无法像 StateNotifier 那样进行扩展,因此我不确定将自定义 setter 和 getter 放在哪里。如果我为每个列表编写一个 FutureProvider,则无法处理 Hive.initFlutter 只应调用一次的事实。我可以找到解决这个问题的方法,但这有点笨拙,并且认为如果有更多经验的人向我提供建议,我会更好。StreamProvider看起来与 FutureProvider 基本相同。也许有一种方法可以在 StateNotifierProvider 中组合 FutureProvider ?真的不知道那会是什么样子。

yur*_*rin 0

我使用这个问题及其评论解决了一个相关问题。由于OP似乎在两年前解决了他的问题,所以我发布这篇文章希望对其他人有所帮助。

在我的应用程序中,我让用户使用toggle_switch选择照明模式 在此输入图像描述

我使用 flutter_riverpod 来管理mode状态,并使用 hive 来保存mode值,这样如果用户选择了dark模式,下次应用程序dark也将以模式启动。

mode_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';

class ModeStateNotifier extends StateNotifier<String> {
  ModeStateNotifier() : super('light');

  void setState(String val) {
    state = val;
    Hive.box('box123').put('mode', val);
  }

  setUp()  {
    String? defaultMode =  Hive.box('box123').get('mode');
    setState(defaultMode ?? 'light');
  }
}

bool needSetUp = true;
final modeProvider = StateNotifierProvider<ModeStateNotifier, String>((ref) {
  final modeStateNotifier = ModeStateNotifier();
  if (needSetUp) {
    modeStateNotifier.setUp();
    needSetUp = false;
  }
  return modeStateNotifier;
});
Run Code Online (Sandbox Code Playgroud)

Hive 在 main 中初始化:

主程序.dart

void main() async {
    await Hive.initFlutter();
    await Hive.openBox('box123');
    runApp(
    const ProviderScope(child: MainApp()),
  );
}
Run Code Online (Sandbox Code Playgroud)

MainApp延伸ConsumerWidget

Widget build(BuildContext context, WidgetRef ref) {
String mode = ref.watch(modeProvider);
return MaterialApp.router(
  routerConfig: rout.Router.getRouter(),
  themeMode: ThemeMode.values.byName(mode);
  ...
Run Code Online (Sandbox Code Playgroud)

当轻按开关时:

onToggle: (index) {
                String value = 'light';
                if (index == 1) value = 'system';
                if (index == 2) value = 'dark';
                ref.read(modeProvider.notifier).setState(value);
              },
Run Code Online (Sandbox Code Playgroud)