family constant
A group of providers that builds their value from an external parameter.
Families can be useful to connect a provider with values that it doesn't have access to. For example:
-
Allowing a "title provider" access the
Locale
final titleFamily = Provider.family<String, Locale>((ref, locale) { if (locale == const Locale('en')) { return 'English title'; } else if (locale == const Locale('fr')) { return 'Titre Français'; } }); // ... @override Widget build(BuildContext context, WidgetRef ref) { final locale = Localizations.localeOf(context); // Obtains the title based on the current Locale. // Will automatically update the title when the Locale changes. final title = ref.watch(titleFamily(locale)); return Text(title); }
-
Have a "user provider" that receives the user ID as a parameter
final userFamily = FutureProvider.family<User, int>((ref, userId) async { final userRepository = ref.read(userRepositoryProvider); return await userRepository.fetch(userId); }); // ... @override Widget build(BuildContext context, WidgetRef ref) { int userId; // Read the user ID from somewhere // Read and potentially fetch the user with id `userId`. // When `userId` changes, this will automatically update the UI // Similarly, if two widgets tries to read `userFamily` with the same `userId` // then the user will be fetched only once. final user = ref.watch(userFamily(userId)); return user.when( data: (user) => Text(user.name), loading: () => const CircularProgressIndicator(), error: (err, stack) => const Text('error'), ); }
-
Connect a provider with another provider without having a direct reference on it.
final repositoryProvider = Provider.family<String, FutureProvider<Configurations>>((ref, configurationsProvider) { // Read a provider without knowing what that provider is. final configurations = await ref.read(configurationsProvider.future); return Repository(host: configurations.host); });
Usage
The way families works is by adding an extra parameter to the provider. This parameter can then be freely used in our provider to create some state.
For example, we could combine family
with FutureProvider to fetch
a Message
from its ID:
final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
return dio.get('http://my_api.dev/messages/$id');
});
Then, when using our messagesFamily
provider, the syntax is slightly modified.
The usual:
Widget build(BuildContext context, WidgetRef ref) {
// Error – messagesFamily is not a provider
final response = ref.watch(messagesFamily);
}
will not work anymore.
Instead, we need to pass a parameter to messagesFamily
:
Widget build(BuildContext context, WidgetRef ref) {
final response = ref.watch(messagesFamily('id'));
}
NOTE: It is totally possible to use a family with different parameters
simultaneously. For example, we could use a titleFamily
to read both
the french and english translations at the same time:
@override
Widget build(BuildContext context, WidgetRef ref) {
final frenchTitle = ref.watch(titleFamily(const Locale('fr')));
final englishTitle = ref.watch(titleFamily(const Locale('en')));
return Text('fr: $frenchTitle en: $englishTitle');
}
Parameter restrictions
For families to work correctly, it is critical for the parameter passed to
a provider to have a consistent hashCode
and ==
.
Ideally the parameter should either be a primitive (bool/int/double/String),
a constant (providers), or an immutable object that override ==
and hashCode
.
-
PREFER using
family
in combination withautoDispose
if the parameter passed to providers is a complex object:final example = Provider.autoDispose.family<Value, ComplexParameter>((ref, param) { });
This ensures that there is no memory leak if the parameter changed and is never used again.
Passing multiple parameters to a family
Families have no built-in support for passing multiple values to a provider.
On the other hand, that value could be anything (as long as it matches with the restrictions mentioned previously).
This includes:
- A tuple (using
package:tuple
) - Objects generated with Freezed/built_value
- Objects based on
package:equatable
This includes:
-
A tuple (using
package:tuple
) -
Objects generated with Freezed/built_value, such as:
@freezed abstract class MyParameter with _$MyParameter { factory MyParameter({ required int userId, required Locale locale, }) = _MyParameter; } final exampleProvider = Provider.family<Something, MyParameter>((ref, myParameter) { print(myParameter.userId); print(myParameter.locale); // Do something with userId/locale }); @override Widget build(BuildContext context, WidgetRef ref) { int userId; // Read the user ID from somewhere final locale = Localizations.localeOf(context); final something = ref.watch( exampleProvider(MyParameter(userId: userId, locale: locale)), ); }
-
Objects based on
package:equatable
, such as:class MyParameter extends Equatable { factory MyParameter({ required this.userId, requires this.locale, }); final int userId; final Local locale; @override List<Object> get props => [userId, locale]; } final exampleProvider = Provider.family<Something, MyParameter>((ref, myParameter) { print(myParameter.userId); print(myParameter.locale); // Do something with userId/locale }); @override Widget build(BuildContext context, WidgetRef ref) { int userId; // Read the user ID from somewhere final locale = Localizations.localeOf(context); final something = ref.watch( exampleProvider(MyParameter(userId: userId, locale: locale)), ); }
Implementation
static const family = ChangeNotifierProviderFamilyBuilder();