可以通过嵌套的 ProviderScope 向子小部件提供“已解析”的 StreamProvider 吗?

问题描述

我正在尝试将“已解决”的 Riverpod StreamProvider 对象注入下面的树中,以删除一些不必要的异步调用。如果我对文档的解释是正确的,嵌套的 ProviderScope 应该对此有所帮助,但我收到运行时异常。

我的用例:我需要访问小部件树中特定于用户specs 对象。整个应用程序的其余部分都需要来自该对象的一些数据,包括作为任何数据库操作的参数。 specs 对象来自 firebase,并与 StreamProvider 异步检索。

一旦在 HomePage 小部件内执行,我知道规范对象必须被加载并且有效,所以我不想再次获取它作为需要处理加载和错误情况的流提供者。在将规范提供程序输入到其他组合提供程序的情况下尤其如此,因为额外的负载错误情况会增加许多不必要的复杂性。

// Called at the root of the tree to retieve some firestore object
final specsStreamProvider = StreamProvider<Specs?>((ref) {
  return ref.read(baseDatabaseProvider).currentSpecs();
});

// Called further down to provide the object that was retrieved
final specsProvider = Provider<Specs>((ref) {
  throw UnimplementedError('should have been overwritten');
});

// An example of how content will be retrieved from firestore at HomePage widget and below.
// Having to use specsStreamProvider here quickly turns into a mess.
final recordStreamProvider = StreamProvider.autodispose<List<Record>>((ref) {
  final specs = ref.read<Specs>(specsProvider);
  final database = ref.read(contentDatabaseProvider(specs.current!));
  return database.recordsstream();
});

class SetupWidget extends ConsumerWidget {
  const SetupWidget({Key? key,required this.setupBuilder,required this.homeBuilder}) : super(key: key);
  final WidgetBuilder setupBuilder;
  final WidgetBuilder homeBuilder;

  @override
  Widget build(BuildContext context,ScopedReader watch) {
    final specsAsyncValue = watch(specsStreamProvider);
    return specsAsyncValue.when(
      data: (specs) => _data(context,specs),loading: () => const Scaffold(/.../),error: (e,__) => Scaffold(/.../),));
  }

  Widget _data(BuildContext context,Specs? specs) {
    if (specs != null) {
      return ProviderScope(
        // The plan here is to introduce the resolved specs into the tree below
        overrides: [specsProvider.overrideWithValue(specs)],child: homeBuilder(context),);
    }
    return setupBuilder(context);
  }
}

根据 Riverpod API,嵌套的 ProviderScope 是覆盖小部件树部分提供者的有效工具。不幸的是,就我而言,我收到运行时错误不支持的操作:无法覆盖非根 ProviderContainer/ProviderScope 上的提供程序”

我还尝试将 specsProvider 设为 ScopedProvider,但组合后的 recordStreamProvider 无法编译。 ('错误:无法将参数类型 'ScopedProvider' 分配给参数类型 'RootProvider'。'

解决方法

认为我想通了。我将 specsProvider 设置为在父级中设置的 ScopedProvider 并将 recordStreamProvider(仅在子级中调用的那个)更改为不直接依赖于作用域提供者。

但是,如果我在这里所做的事情是可以接受的并且没有反模式,我仍然很乐意听取一位 Riverpod 专家的意见。

父设置范围提供程序:

final specsStreamProvider = StreamProvider<Specs?>((ref) {
  return ref.read(baseDatabaseProvider).currentSpecs();
});

// Called further down to provide the object that was retrieved
// This MUST be a ScopedProvider
final specsProvider = ScopedProvider<Specs>((ref) {
  throw UnimplementedError('should have been overwritten');
});

class SetupWidget extends ConsumerWidget {/* as before */}

使用范围提供程序的儿童

// no dependency on specsProvider here
final recordStreamProvider = StreamProvider.family.autoDispose<List<Record>,String>((ref,storeId) {
  final database = ref.read(contentDatabaseProvider(storeId));
  return database.recordsStream();
});

class HomePage extends ConsumerWidget {
@override
  Widget build(BuildContext context,ScopedReader watch) {
    final specs = watch(specsProvider);
    final recordsAsyncValue = watch(recordsStreamProvider(specs.storeId!));

    return recordsAsyncValue.when(
      data: (records) => /* build a list */
      loading: () => /* show a progress indicator */,error: (e,__) => /* show an alert dialog */,));
  }
}