.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
.familyper ottenere (fetch) unMessagedal suo ID. - Passare il
Localecorrente 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)),
);
...
}