Salta al contenuto principale

Passare parametri alle tue richieste

In un articolo precedente abbiamo visto come possiamo definire un "provider" per effettuare una semplice richiesta GET. Spesso però, le richieste HTTP dipendono da parametri esterni.

Per esempio, in precedenza abbiamo usato le Bored API per consigliare un'attività casuale agli utenti. Ma probabilmente gli utenti vorrebbero avere il modo di filtrare il tipo di attività che vogliono fare, o avere dei requisiti di prezzo, etc... Questi parametri non sono conosciuti in anticipo. Abbiamo quindi bisogno di un modo per passare questi parametri dalla nostra UI ai nostri provider.

Aggiornare i provider per accettare parametri

Come promemoria, in precedenza abbiamo definito il nostro provider in questo modo:

// Un provider "funzionale"

Future<Activity> activity(ActivityRef ref) async {
// TODO: eseguire una richiesta di rete per ottenere un'attività.
return fetchActivity();
}

// O in alternativa, un "notifier"

class ActivityNotifier2 extends _$ActivityNotifier2 {
/// Notifier arguments are specified on the build method.
/// There can be as many as you want, have any name, and even be optional/named.

Future<Activity> build(String activityType) async {
// Gli argomenti sono disponibili anche tramite "this.<argumentName>"
print(this.activityType);

// TODO: eseguire una richiesta di rete per ottenere un'attività.
return fetchActivity();
}
}

Per passare parametri ai nostri provider, possiamo semplicemente aggiungere i nostri parametri alla funzione annotata stessa. Per esempio, potremmo aggiornare il nostro provider per accettare un parametro String che corrisponde alla tipologia dell'attività desiderata:


Future<Activity> activity(
ActivityRef ref,
// Possiamo aggiungere argomenti al provider
// Il tipo del parametro può essere qualsiasi cosa tu voglia.
String activityType,
) async {
// Possiamo usare l'argomento "activityType" per costruire l'URL
// Questo punterà a "https://boredapi.com/api/activity?type=<activityType>"
final response = await http.get(
Uri(
scheme: 'https',
host: 'boredapi.com',
path: '/api/activity',
// Nessun bisogno di codificare i parametri di query, la classe "Uri" lo fa al posto nostro.
queryParameters: {'type': activityType},
),
);
final json = jsonDecode(response.body) as Map<String, dynamic>;
return Activity.fromJson(json);
}
caution

Quando si passano parametri ai provider è fortemente incoraggiato abilitare "autoDispose" sul provider. Consulta Svuotare la cache e reagire alla cancellazione dello stato per più dettagli.

Aggiornare l'UI per passare i parametri

Precedentemente, i widget consumavano i nostri provider in questo modo:

    AsyncValue<Activity> activity = ref.watch(activityProvider);

Tuttavia, ora, dato che il nostro provider riceve dei parametri, la sintassi per consumarlo è leggermente differente. Il provider ora è una funzione, che ha bisogno di essere invocata con i parametri richiesti. Potremmo aggiornare la nostra UI per passare un valore di attività hard-coded in questo modo:

    AsyncValue<Activity> activity = ref.watch(
// Il provider è ora una funzione che si aspetta il tipo dell'attività
// Passiamo una costante stringa per ora, per semplicità.
activityProvider('recreational'),
);

I parametri passati al provider corrispondono ai parametri della funzione annotata, meno il parametro "ref".

info

È interamente possibile ascoltare lo stesso provider con parametri differenti simultaneamente. Per esempio, la nostra UI potrebbe visualizzare entrambe le attività "recreational" e "cooking":

    return Consumer(
builder: (context, ref, child) {
final recreational = ref.watch(activityProvider('recreational'));
final cooking = ref.watch(activityProvider('cooking'));

// Possiamo quindi visualizzare entrambe le attività.
// Entrambe le richieste avverranno in parallelo e verranno correttamente cachate.
return Column(
children: [
Text(recreational.valueOrNull?.activity ?? ''),
Text(cooking.valueOrNull?.activity ?? ''),
],
);
},
);

Considerazioni riguardo la cache e restrizioni dei parametri

Quando si passano parametri ai provider, la computazione è comunque memorizzata (cachata). La differenza è che la computazione è ora memorizzata per argomento.

Questo significa che se due widget consumano lo stesso provider con gli stessi parametri, solo una singola richiesta di rete verrà eseguita. Tuttavia, se due widget consumano lo stesso provider ma con parametri differenti, verranno effettuate due richieste di rete.

Affinché ciò funzioni, Riverpod si affida all'operatore == dei parametri. Pertanto, è importante che i parametri passati al provider abbiano un'uguaglianza costante.

caution

Un errore comune è di istanziare direttamente un nuovo oggetto come parametro di un provider quando quell'oggetto non sovrascrive ==. Per esempio, potresti essere tentato di passare una List come in questo caso:

    // Potremmo aggiornare activityProvider per accettare direttamente una lista di stringhe.
// Poi essere tentati di creare quella lista direttamente nella chiamata di watch.
ref.watch(activityProvider(['recreational', 'cooking']));

Il problema con questo codice è che ['recreational', 'cooking'] == ['recreational', 'cooking'] è false. Pertanto, Riverpod considererà quei due parametri differenti e tenterà di effettuare una nuova richiesta di rete. Ciò potrebbe risultare in un loop infinito di richieste di rete, facendo vedere in modo permanente un indicatore di progresso all'utente.

Per correggere questo, potresti usare sia una lista const (const ['recreational', 'cooking']) oppure usare un'implementazione di lista personalizzata che sovrascrive==.

Per facilitare l'individuazione dell'errore, è consigliato usare riverpod_lint ed abilitare la regola lint provider_parameters. Fatto ciò, lo snippet precedente mostrerebbe un warning. Consulta Introduzione per i passaggi di installazione.