.family
Prima di leggere questa sezione, considera di leggere providers e come leggerli.
In questa sezione parleremo in dettaglio del modificatore di provider .family
.
Il modificatore .family
ha uno scopo: ottenere un unico provider basato su parametri esterni.
Alcuni casi di uso comune di family
sono:
- Combinare FutureProvider con
.family
per ottenere (fetch) unMessage
dal suo ID. - Passare il
Locale
corrente ad un provider, cosicchè si possano gestire le traduzioni.
Uso
Il modo in cui funzionano le famiglie consiste nell'aggiungere un parametro aggiuntivo al provider. Questo parametro può essere poi liberamente usato nel nostro provider per creare uno stato.
Per esempio, potremmo combinare family
con FutureProvider per ottenere un Message
dal suo ID:
final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
return dio.get('http://my_api.dev/messages/$id');
});
Successivamente, quando andremo ad usare il provider messagesFamily
, la sintassi sarà
leggermente differente dalla solita.
La sintassi abituale non funzionerà più:
Widget build(BuildContext context, WidgetRef ref) {
// Errore – messagesFamily non è un provider
final response = ref.watch(messagesFamily);
}
Dovremo passare invece un parametro a messagesFamily
:
Widget build(BuildContext context, WidgetRef ref) {
final response = ref.watch(messagesFamily('id'));
}
É possibile usare una famiglia con diversi parametri simultaneamente.
Ad esempio, potremmo usare un titleFamily
per leggere entrambe le traduzioni francesi e
inglesi nello stesso momento:
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');
}
Restrizioni dei parametri
Affinchè le famiglie possano funzionare correttamente, è cruciale per il parametro passato
al provider di avere un coerente hashCode
e ==
.
Idealmente, il parametro potrebbe essere sia un tipo primitivo (bool/int/String), una costante (i provider) oppure un oggetto immutabile che sovrascrive ==
e hashCode
.
PREFERIRE l'uso di autoDispose
quando il parametro non è una constante:
Potresti voler usare le famiglie per passare l'input di un campo di ricerca al tuo provider.
Ma quel valore potrebbe cambiare spesso e non essere più utilizzato.
Ciò può causare memory leaks siccome, per default, un provider non è mai distrutto anche se non più usato.
Usare sia .family
che .autoDispose
ci permette di evitare quella perdita di memoria (memory leak).
final characters = FutureProvider.autoDispose.family<List<Character>, String>((ref, filter) async {
return fetchCharacters(filter: filter);
});
Passare più parametri ad una famiglia
Le famiglie non hanno un supporto costruito internamente per passare più valori ad un provider.
D'altro canto però, quel valore potrebbe essere qualsiasi oggetto (purchè corrisponda alle restrizioni menzionate precedentemente).
Possiamo dunque passare più parametri tramite:
- Una tupla di tupla.
- Oggetti generati con Freezed o built_value.
- Oggetti che usano equatable.
Di seguito un esempio che usa Freezed o equatable:
- Freezed
- Equatable
abstract class MyParameter with _$MyParameter {
factory MyParameter({
required int userId,
required Locale locale,
}) = _MyParameter;
}
final exampleProvider = Provider.autoDispose.family<Something, MyParameter>((ref, myParameter) {
print(myParameter.userId);
print(myParameter.locale);
// Logica usando userId/locale
});
Widget build(BuildContext context, WidgetRef ref) {
int userId; // Legge l'ID utente da qualche parte
final locale = Localizations.localeOf(context);
final something = ref.watch(
exampleProvider(MyParameter(userId: userId, locale: locale)),
);
...
}
class MyParameter extends Equatable {
MyParameter({
required this.userId,
required this.locale,
});
final int userId;
final Locale locale;
List<Object> get props => [userId, locale];
}
final exampleProvider = Provider.family<Something, MyParameter>((ref, myParameter) {
print(myParameter.userId);
print(myParameter.locale);
// Logica usando userId/locale
});
Widget build(BuildContext context, WidgetRef ref) {
int userId; // Legge l'ID utente da qualche parte
final locale = Localizations.localeOf(context);
final something = ref.watch(
exampleProvider(MyParameter(userId: userId, locale: locale)),
);
...
}