Можно ли предоставить разрешенный StreamProvider дочерним виджетам через вложенный ProviderScope?

Я пытаюсь вставить разрешенный объект Riverpod StreamProvider в дерево ниже, чтобы удалить некоторые ненужные асинхронные вызовы. Если моя интерпретация документов верна, вложенный ProviderScope должен помочь с этим, но я получаю исключение времени выполнения.

Мой вариант использования: мне нужно получить доступ к объекту 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);
  }
}

Согласно API Riverpod вложенный ProviderScope является допустимым инструментом для перезаписи поставщиков для части дерева виджетов. К сожалению, в моем случае я получаю сообщение об ошибке выполнения: «Неподдерживаемая операция: невозможно переопределить поставщиков на некорневом ProviderContainer / ProviderScope».

Я также пытался сделать specsProvider ScopedProvider, но тогда объединенный recordStreamProvider не компилируется. ('ошибка: тип аргумента' ScopedProvider 'не может быть назначен типу параметра' RootProvider ‹Object ?, Specs› '.'


person duffy    schedule 18.05.2021    source источник


Ответы (1)


Я думаю, что понял. Я сделал 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 */,
      ));
  }
}
person duffy    schedule 18.05.2021