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.

// We can specify "keepAlive" in the annotation to disable
// the automatic state destruction
(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>();

// When the state is destroyed, we close the StreamController.
ref.onDispose(controller.close);

// TO-DO: Push some values in the 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: () {
// On click, destroy the 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() {
// Invalidate all possible parameter combinations of this provider.
ref.invalidate(labelProvider);
// Invalidate a specific combination only
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'));
// We keep the provider alive only after the request has successfully completed.
// If the request failed (and threw), then when the provider stops being
// listened, the state will be destroyed.
ref.keepAlive();

// We can use the `link` to restore the auto-dispose behavior with:
// 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?> {
/// Keeps the provider alive for [duration].
void cacheFor(Duration duration) {
// Immediately prevent the state from getting destroyed.
final link = keepAlive();
// After duration has elapsed, we re-enable automatic disposal.
final timer = Timer(duration, link.close);

// Optional: when the provider is recomputed (such as with ref.watch),
// we cancel the pending timer.
onDispose(timer.cancel);
}
}

Infine, possiamo usarlo in questo modo:


Future<Object> example(ExampleRef ref) async {
/// Keeps the state alive for 5 minutes
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.