Salta al contenuto principale

Inizializzazione anticipata dei provider

Tutti i provider sono inizializzati in modo lazy di default. Questo significa che il provider è inizializzato solo quando viene usato per la prima volta. Tale comportamento è utile per provider che sono utilizzati solo in certe parti dell'applicazione.

Sfortunatamente, non c'è nessun modo per marcare un provider che necessita di essere inizializzato anticipatamente a causa del funzionamento di Dart (per scopi di tree shaking). Una soluzione, tuttavia, è forzare la lettura dei provider che vuoi inizializzare subito alla radice della tua applicazione.

L'approccio consigliato è di semplicemente "osservare" un provider in un Consumer posto proprio sotto ProviderScope:

void main() {
runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {

Widget build(BuildContext context) {
return const _EagerInitialization(
// TODO: Renderizza la tua app qui
child: MaterialApp(),
);
}
}

class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;


Widget build(BuildContext context, WidgetRef ref) {
// Inizializza anticipatamente i provider osservandoli.
// Usando "watch", il provider resterà in vita e non sarà distrutto.
ref.watch(myProvider);
return child;
}
}
note

Considera mettere il consumer di inizializzazione nel tuo widget "MyApp" o in un widget pubblico. Ciò permette ai tuoi test di utilizzare lo stesso comportamento, rimuovendo la logica dal tuo metodo 'main'.

FAQ

Fare questo non provocherà la ricostruzione dell'intera applicazione quando il provider cambia?

No, non in questo caso. Nell'esempio mostrato sopra, il consumer responsabile per l'inizializzazione anticipata è un widget separato, che non fa nulla a parte ritornare un child.

La parte chiave è che ritorna un child, anziché istanziare la stessa MaterialApp. Ciò significa che se _EagerInitialization viene ricostruito, la variabile child non sarà cambiata. E quando un widget non cambia, Flutter non lo ricostruisce.

Pertanto, solo _EagerInitialization verrà ricostruito, a meno che un altro widget starà anch'esso ascoltando quel provider.

Usando questo approccio, come posso gestire gli stati di errore e caricamento?

Puoi gestire gli stati di errore/caricamento come faresti normalmente in un Consumer. Il tuo _EagerInitialization potrebbe controllare se un provider è nello stato di caricamento, se sì, restituire un CircularProgressIndicator invece del child:

class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;


Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(myProvider);

// Gestisce gli stati di errore e di caricamento
if (result.isLoading) {
return const CircularProgressIndicator();
} else if (result.hasError) {
return const Text('Oopsy!');
}

return child;
}
}

Ho gestito gli stati di errore/caricamento, ma altri Consumer continuano a ricevere un AsyncValue! C'è un modo per non dover gestire gli stati di errore/caricamento in ogni widget?

Piuttosto che cercare di non fare esporre un AsyncValue al tuo provider, potresti invece utilizzare AsyncValue.requireValue nei tuoi widget. Questo leggerà il dato senza dover fare pattern matching. E nel caso un bug spunti fuori, tirerà un eccezione con un messaggio chiaro.

// Un provider inizializzato anticipatamente.

Future<String> example(ExampleRef ref) async => 'Hello world';

class MyConsumer extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(exampleProvider);

// Se il provider è stato correttamente inizializzato anticipatamente, allora puoi
// direttamente leggere il dato con "requireValue".
return Text(result.requireValue);
}
}
note

Nonostante ci siano dei modi per non esporre gli stati di caricamento/errore in questi casi, è generalmente sconsigliato farlo. La complessità aggiuntiva derivante dalla creazione di due provider e dall'utilizzo degli override non vale la pena.