Prince
Prince

Reputation: 253

How to use a riverpod family provider without passing parameter

Is there a way to access an instance of a provided ".family" change notifier (which has already been instantiated, passing the right parameters) without passing the parameters again?

IN PROVIDER

When you create a provider of a ChangeNotifier (which requires parameters), you can get the same change notifier it's providing with Provider.of<ChangeNotif>(context);

class ChangeNotif extends ChangeNotifier {
  final String id;
  const ChangeNotif(this.id);
}

ChangeNotifierProvider(create: (_) => ChangeNotif("dash"));

Once the provider has been created with its right params, you can get whatever it's providing anywhere down that widget tree without any syntax like Provider.of<ChangeNotif("dash")>(context) but rather Provider.of<ChangeNotif>(context).

IN RIVERPOD

Since you have to pass the parameters to the provider when getting an instance of it, I've had to assign the provider to a variable in order to pass it down to its children which need the change notifier the provider is providing.

final changeNotifProvider = ChangeNotifierProvider.family<ChangeNotif, String>((ref, id) => ChangeNotif(id));

class A extends HookWidget {
  build() {
    final _changeNotifProvider = changeNotifProvider("dash");
    final _changeNotif = useProvider(_changeNotifProvider);

    return Column(
     children: [
       B(),
       c(_changeNotifProvider),
     ]
    );
  }
}

Is there a way to get the instantiated _changeNotif without passing it as a parameter to a child widget? Is there any way to get the same instance of _changeNotif in another widget that isn't a child of A (like using Provider.of<ChangeNotif>(context) in Provider without having to pass new parameters)?

Upvotes: 12

Views: 6457

Answers (2)

TmKVU
TmKVU

Reputation: 3000

I use this extension to get all the instances for StateNotifierProviderFamily instances, but you could also do this for ChangeNotifierProviderFamily. You do need to register them in the provider:

extension StateNotifierProviderFamilyExtension<
    Notifier extends StateNotifier<Value>,
    Value,
    Param> on StateNotifierProviderFamily<Notifier, Value, Param> {
  /// Get all the registered versions of this `StateNotifierProviderFamily`
  ///
  /// This function requires a reader to the current `ProviderScope` to
  /// get the `familyKeysStorageProvider`
  ///
  /// The family needs to be registered with the `familyKeysStorageProvider`,
  /// like this:
  ///
  /// ```dart
  /// final keyStorage = ref.read(familyKeysStorageProvider(myProvider));
  /// keyStorage.state.add(myKey);
  /// ```
  Iterable<T> all<T extends StateNotifierProvider<Notifier, Value>>(
      Reader read) {
    return read(_familyKeysStorageProvider(this))
        .state
        .map((key) => call(key) as T);
  }

  Set<dynamic> keys(Reader read) =>
      read(_familyKeysStorageProvider(this)).state;
  void addKey(Reader read, dynamic key) => this.keys(read).add(key);
}

/// Use this provider to register keys for a `StateNotifierProviderFamily`
/// object.
///
/// After registration of the keys, it is possible to retrieve all registered
/// instances with the `all(Reader read)` method on `StateNotifierProviderFamily`
final _familyKeysStorageProvider =
    StateProvider.family<Set<dynamic>, StateNotifierProviderFamily>((ref, key) {
  return {};
});

You can register like this:

final myStateProvider = StateNotifierProvider.family<
    MyStateNotifier,
    MyState,
    MyKey>(
  (ref, key) {
    myStateProvider.addKey(ref.read, key);
    return MyStateNotifier(ref.read, key);
  },
);

Of course, you also need to import the extension for this to work.

For the ChangeNotifierProviderFamily I think it would be something like this (I have not tested this):

extension ChangeNotifierProviderFamilyExtension<Notifier extends ChangeNotifier,
    Param> on ChangeNotifierProviderFamily<Notifier, Param> {
  Iterable<T> all<T extends ChangeNotifierProvider<Notifier>>(Reader read) {
    return read(_notifierFamilyKeysStorageProvider(this))
        .state
        .map((key) => call(key) as T);
  }

  Set<dynamic> keys(Reader read) =>
      read(_notifierFamilyKeysStorageProvider(this)).state;
  void addKey(Reader read, dynamic key) => this.keys(read).add(key);
}

final _notifierFamilyKeysStorageProvider =
    StateProvider.family<Set<dynamic>, ChangeNotifierProviderFamily>(
        (ref, key) {
  return {};
});

Upvotes: 1

EdwynZN
EdwynZN

Reputation: 5601

Check the riverpod examples of how it solves that problem Marvel Example

The advantage of using riverpo family vs Provider package is that provider doesn't let you read multiple instances of the same class down the widget tree and riverpod family doesn't have that restriction

in Provider

MultiProvider(
   providers: [
      ChangeNotifierProvider<ChangeNotif>(
        create: (_) => ChangeNotif('dash')
      ),
      ChangeNotifierProvider<ChangeNotif>(
        create: (_) => ChangeNotif('dash2')
      ),
    // and many more of the same provider but different initial value
   ].
   child: MyWidget()
);

Then in MyWidget when doing Provider.of<ChangeNotif>(context) you only get the last one created by MultiProvider, cannot differentiate multiples providers of the same type, now in riverpod a solution to that and a way to pass down the family would be to use a ScopedProvider with the initial value.

final _idProvider = ScopedProvider<String>(null);

class A extends HookWidget {

  @override
  Widget build() {
    final _changeNotif = useProvider(changeNotifProvider("dash"));

    return ProviderScope(
      overrides: [
         _idProvider .overrideWithValue("dash"),
      ],
      child: Column(
        children: [
          B(),
          c(),
        ]
      )
    );
  }
}

Now all widgets down (B and C) can read the parameter used to create the family, if the provider already exists it will retrieve it without creating it anew

class A extends HookWidget {

  @override
  Widget build() {
    final id = useProvider(_idProvider);
    final _changeNotif = useProvider(changeNotifProvider(id)); //the ProviderContainer will check if there is already a family with that id and retrieve it

    return Text(_changeNotif.id);
  }
}

Upvotes: 1

Related Questions