Ana içeriğe atla

Provider'lar nasıl önden başlatılır

Tüm provider'lar varsayılan olarak tembel (lazy) şekilde başlatılır. Yani bir provider ancak ilk kez kullanıldığında başlatılır. Bu, yalnızca uygulamanın belirli bölümlerinde kullanılan provider'lar için oldukça kullanışlıdır.

Ne yazık ki Dart'ın çalışma biçimi gereği (tree shaking amacıyla), bir provider'ı önden başlatılması (eager initialization) gerektiği şeklinde işaretlemenin bir yolu yok. Ancak bir çözüm var: önden başlatmak istediğiniz provider'ları uygulamanızın kökünde zorla okumak.

Önerilen yaklaşım, ProviderScopeunuzun hemen altına yerleştirilmiş bir Consumer içinde provider'ı basitçe "watch" etmektir:

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

class MyApp extends StatelessWidget {

Widget build(BuildContext context) {
return const _EagerInitialization(
// TODO: Uygulamanızı burada oluşturun
child: MaterialApp(),
);
}
}

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


Widget build(BuildContext context, WidgetRef ref) {
// Provider'ları dinleyerek önden başlatın.
// "watch" kullanıldığında provider hayatta kalır ve yok edilmez.
ref.watch(myProvider);
return child;
}
}
NOT

Başlatmayı yapan consumer'ı "MyApp" içine ya da herkese açık (public) bir widget içine koymayı düşünün. Böylece mantığı main'inizden çıkararak testlerinizin de aynı davranışı kullanmasını sağlarsınız.

SSS

Provider değiştiğinde bu, uygulamamızın tamamını yeniden oluşturmaz mı?

Hayır, öyle olmaz. Yukarıdaki örnekte, önden başlatmadan sorumlu consumer ayrı bir widget'tır ve bir child döndürmek dışında hiçbir şey yapmaz.

Kilit nokta, MaterialAppi kendisinin oluşturması yerine bir child döndürmesidir. Bu da şu anlama gelir: _EagerInitialization yeniden oluşturulsa bile child değişkeni değişmemiş olur. Bir widget değişmediğinde ise Flutter onu yeniden oluşturmaz.

Dolayısıyla, başka bir widget da o provider'ı dinlemiyorsa yalnızca _EagerInitialization yeniden oluşturulur.

Bu yaklaşımı kullanırken yükleme ve hata durumlarını nasıl yönetebilirim?

Yükleme/hata durumlarını bir Consumer içinde normalde nasıl yönetiyorsanız öyle yönetebilirsiniz. _EagerInitialization widget'ınız bir provider'ın "loading" durumunda olup olmadığını kontrol edebilir ve öyleyse child yerine bir CircularProgressIndicator döndürebilir:

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


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

// Hata ve yükleme durumlarını yönetin
if (result.isLoading) {
return const CircularProgressIndicator();
} else if (result.hasError) {
return const Text('Oopsy!');
}

return child;
}
}

Yükleme/hata durumlarını yönettim ama diğer Consumer'lar hâlâ bir AsyncValue alıyor! Her widget'ta yükleme/hata durumlarını yönetmek zorunda kalmamanın bir yolu var mı?

Provider'ınızın bir AsyncValue sunmamasını sağlamaya çalışmak yerine, widget'larınızın AsyncValue.requireValue kullanmasını sağlayabilirsiniz.
Bu sayede desen eşleme (pattern matching) yapmadan veriyi doğrudan okursunuz. Gözden kaçan bir hata olması durumunda ise net bir mesajla bir istisna fırlatır.

// Önden başlatılmış bir provider.
final exampleProvider = FutureProvider<String>((ref) async => 'Hello world');

class MyConsumer extends ConsumerWidget {

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

/// Provider doğru şekilde önden başlatıldıysa, veriyi doğrudan
/// "requireValue" ile okuyabiliriz.
return Text(result.requireValue);
}
}
NOT

Bu durumlarda yükleme/hata durumlarını dışarıya hiç açmamanın yolları olsa da (kapsamlamaya (scoping) dayanarak), bunu yapmak genellikle önerilmez.
İki provider oluşturmanın ve override kullanmanın getirdiği ek karmaşıklık zahmetine değmez.