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: Render your app here
child: MaterialApp(),
);
}
}
class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;
Widget build(BuildContext context, WidgetRef ref) {
// Eagerly initialize providers by watching them.
// By using "watch", the provider will stay alive and not be disposed.
ref.watch(myProvider);
return child;
}
}
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);
// Handle error states and loading states
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.
// An eagerly initialized provider.
Future<String> example(Ref ref) async => 'Hello world';
class MyConsumer extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(exampleProvider);
/// If the provider was correctly eagerly initialized, then we can
/// directly read the data with "requireValue".
return Text(result.requireValue);
}
}
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.