Salta al contenuto principale

.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) un Message 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'));
}
info

É 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:

@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');
}

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:

Di seguito un esempio che usa Freezed o equatable:

@freezed
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
});

@override
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)),
);

...
}