Salta al contenuto principale

Svuotare la cache e reagire alla cancellazione dello stato

Fino ad ora, abbiamo visto come creare/aggiornare lo stato dell'applicazione. Ma non abbiamo ancora parlato di quando avviene la distruzione dello stato.

Riverpod offre vari modi per interagire con la cancellazione dello stato. Questo va dal ritardare la cancellazione dello stato a reagire alla distruzione.

Quando lo stato è distrutto e come cambiare questo?

Se si usa la generazione di codice, di default, lo stato è distrutto quando il provider non viene più ascoltato. Questo accade quando un listener non ha nessun listener attivo per un intero frame. Qunado questo accade, lo stato viene distrutto.

È possibile disattivare questo comportamento utilizzando keepAlive: true. Fare ciò impedirà che lo stato venga distrutto quando tutti i listener vengono rimossi.

// Possiamo specificare "keepAlive" nell'annotazione per disabilitare
// la distruzione automatica dello stato
(keepAlive: true)
int example(ExampleRef ref) {
return 0;
}
note

Abilitare/disabilitare la rimozione automatica non ha alcun impatto su quando lo stato viene distrutto quando il provider viene ricomputato. Lo stato sarà sempre distrutto quando il provider viene ricomputato.

caution

Quando i provider ricevono dei parametri è consigliato abilitare la rimozione automatica. Questo perché, in caso contrario, verrà creato uno stato per ogni combinazione di parametri, il che potrebbe causare perdite di memoria.

Reagire alla rimozione dello stato

In Riverpod, ci sono alcuni modi integrati per distruggere lo stato:

  • Il provider non è più usato ed nella modalità "auto rimozione" (ne parleremo più avanti) In questo caso, tutto lo stato associato con il provider verrà distrutto.
  • Il provider è ricomputato, come con ref.watch. In questo caso, lo stato precedente è rimosso ed un nuovo stato viene creato.

In entrambi casi potresti voler eseguire della logica quando questo accade. Questo può essere realizzato con ref.onDispose. Questo metodo consente di registrare un listener ogni volta che lo stato viene distrutto.

Per esempio, potresti voler usarlo per chiudere qualsiasi StreamController attivo:


Stream<int> example(ExampleRef ref) {
final controller = StreamController<int>();

// Quando lo stato viene distrutto chiudiamo lo StreamController.
ref.onDispose(controller.close);

// TO-DO: Aggiungere dei valori nello StreamController
return controller.stream;
}
caution

La callback di ref.onDispose non deve provocare side-effects. Modificare i provider dentro onDispose potrebbe condurre a comportamenti inaspettati.

info

Ci sono altri cicli di vita utili, come:

  • ref.onCancel il quale è chiamato quando l'ultimo listenere di un provider viene rimosso.
  • ref.onResume il quale è chiamato quando un nuovo listener viene aggiunto dopo che onCancel è stato invocato.
info

Puoi chiamare ref.onDispose quante volte desideri. Sentiti libero di chiamarlo una volta per ogni oggetto eliminabile nel tuo provider. Questa pratica rende più facile individuare quando dimentichiamo di eliminare qualcosa.

Forzare manualmente la distruzione di un provider usando ref.invalidate

Alcune volte potresti voler forzare la distruzione di un provider. Questo può essere fatto usando ref.invalidate, il quale può essere chiamato da un altro provider o da un widget.

Usare ref.invalidate distruggerà lo stato corrente del provider. Ci sono due possibili risultati:

  • Se il provider è ascoltato, un nuovo stato sarà creato.
  • Se il provider non è ascoltato, il provider sarà completamente distrutto.
class MyWidget extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// Sul click, distruggiamo il provider.
ref.invalidate(someProvider);
},
child: const Text('dispose a provider'),
);
}
}
info

È possibile per i provider invalidare se stessi usando ref.invalidateSelf. Anche se in questo caso, ciò risulterà sempre nella creazione di un nuovo stato.

tip

Quando si prova ad invalidare un provider che riceve dei parametri, è possible sia invalidare una specifica combinazione di parametri, sia tutte le combinazioni in una volta sola:


String label(LabelRef ref, String userName) {
return 'Hello $userName';
}

// ...

void onTap() {
// Invalida tutte le possibili combinazioni di paramatri di questo provider.
ref.invalidate(labelProvider);
// Invalida una sola specifica combinazione
ref.invalidate(labelProvider('John'));
}

Rimozione ottimizzata con ref.keepAlive

Come menzionato prima, quando la rimozione automatica è abilitata, lo stato viene distrutto quando il provider non ha più ascoltatori per un intero frame.

Ma potresti voler avere maggiore controllo su questo comportamento. Per esempio, potresti voler mantenere lo stato delle richieste di rete completate, ma non memorizzare quelle invece fallite.

Questo può essere ottenuto con ref.keepAlive, dopo aver abilitato la rimozione automatica. Usandolo, puoi decidere quando lo stato smette di essere rimosso automaticamente.


Future<String> example(ExampleRef ref) async {
final response = await http.get(Uri.parse('https://example.com'));
// Manteniamo il provider in vita solo se la richiesta è stata completata correttamente.
// Se la richiesta ha fallito, quando il provider verrà smesso di essere ascoltato,
// lo stato verrà distrutto.
ref.keepAlive();

// Possiamo usare il `link` per ristabilire il comportamento di auto-rimozione con:
// link.close();

return response.body;
}
note

Se il provider viene ricomputato la rimozione automatica sarà riabilitata.

È anche possibile utilizzare il valore restituito da ref.keepAlive per tornare alla rimozione automatico.

Esempio: mantenere lo stato in vita per una specifica quantità di tempo

Attualmente, Riverpod non offre un modo integrato per mantere lo stato in vita per una specifica quantità di tempo. Tuttavia possiamo implementare tale feature facilmente e in modo riutilizzabile con gli strumenti che abbiamo visto fino ad ora.

Usando un Timer + ref.keepAlive, possiamo mantenere lo stato attivo per una specifica quantità di tempo. Per rendere questa logica riutilizzabile, potremmo implementarla come metodo di una estensione.

extension CacheForExtension on AutoDisposeRef<Object?> {
/// Mantiene il provider in vita per [duration].
void cacheFor(Duration duration) {
// Previene subito lo stato dal essere distrutto.
final link = keepAlive();
// Dopo che la durata è terminata, riabilitiamo la rimozione automatica
final timer = Timer(duration, link.close);

// Opzionale: quando il provider viene ricomputato (come con ref.watch),
// cancelliamo il timer rimasto attivo.
onDispose(timer.cancel);
}
}

Infine, possiamo usarlo in questo modo:


Future<Object> example(ExampleRef ref) async {
// Mantiene lo stato attivo per 5 minuti
ref.cacheFor(const Duration(minutes: 5));

return http.get(Uri.https('example.com'));
}

Questa logica può essere affinata a seconda delle tue necessità. Per esempio, potresti usare ref.onCancel/ref.onResume per distruggere lo stato solo se un provider non è stato ascoltato per un determinato periodo di tempo.