Provider'lar
Provider'lar Riverpod'un merkezî özelliğidir. Riverpod kullanıyorsanız, onu provider'ları için kullanıyorsunuzdur.
Provider nedir?
Provider'lar özünde, üzerine biraz şeker serpiştirilmiş "hafızalanmış (memoized) fonksiyonlardır".
Bunun anlamı şudur: provider'lar, aynı parametrelerle çağrıldıklarında önbelleğe alınmış bir değer
döndüren fonksiyonlardır.
Provider kullanmanın en yaygın kullanım senaryosu bir ağ isteği yapmaktır.
Bir API'den kullanıcı çeken bir fonksiyon düşünün:
Future<User> fetchUser() async {
final response = await http.get('https://api.example.com/user/123');
return User.fromJson(response.body);
}
Bu fonksiyonun bir sorunu şu: onu widget'ların içinde kullanmaya kalksaydık, sonucu kendimiz önbelleğe almamız; ardından değeri ihtiyaç duyan tüm widget'lar arasında paylaşmanın bir yolunu bulmamız gerekirdi.
İşte provider'lar burada devreye giriyor. Provider'lar fonksiyonların etrafındaki sarmalayıcılardır. Söz konusu fonksiyonun sonucunu önbelleğe alır ve birden fazla widget'ın aynı değere erişmesine izin verirler:
- riverpod
- riverpod_generator
// fetchUser fonksiyonumuzun eşdeğeri, ancak sonuç önbelleğe alınır.
// userProvider'ı birden çok kez kullanmak aynı değeri döndürecektir.
final userProvider = FutureProvider<User>((ref) async {
final response = await http.get('https://api.example.com/user/123');
return User.fromJson(response.body);
});
// fetchUser fonksiyonumuzun eşdeğeri, ancak sonuç önbelleğe alınır.
// Bu bir "userProvider" üretecektir. Onu birden çok kez kullanmak
// aynı değeri döndürecektir.
Future<User> user(Ref ref) async {
final response = await http.get('https://api.example.com/user/123');
return User.fromJson(response.body);
}
Temel önbelleklemenin üzerine provider'lar, kendilerini daha güçlü kılmak için çeşitli özellikler ekler:
- Yerleşik önbellek geçersizleştirme mekanizmaları
Özellikle Ref.watch, önbellekleri birbiriyle birleştirmenize ve gerekli olanı otomatik olarak geçersizleştirmenize olanak tanır. - Otomatik yok etme
Provider'lar artık gerek duyulmadığında kaynakları otomatik olarak serbest bırakabilir. - Veri bağlama
Provider'lar FutureBuilder veya StreamBuilder ihtiyacını ortadan kaldırır. - Otomatik hata yönetimi
Provider'lar hataları otomatik olarak yakalayıp kullanıcı arayüzüne sunabilir. - Sahteleme desteği
Daha iyi test edebilmek ve başka amaçlar için tüm provider'lar sahtelenebilir. Bkz. Provider'ları geçersiz kılma. - Çevrimdışı kalıcılık (deneysel)
Bir provider'ın sonucu diske kalıcı olarak yazılabilir ve uygulama yeniden başlatıldığında otomatik olarak geri yüklenebilir. - Mutation'lar (deneysel)
Provider'lar, form gönderimi gibi yan etkiler için kullanıcı arayüzlerinin bir yükleme göstergesi/hata durumu göstermesine yönelik yerleşik bir yol sunar.
Provider'lar 6 çeşitte gelir:
| Senkron | Future | Stream | |
|---|---|---|---|
| Değiştirilemez | Provider | FutureProvider | StreamProvider |
| Değiştirilebilir | NotifierProvider | AsyncNotifierProvider | StreamNotifierProvider |
Bu ilk bakışta göz korkutucu görünebilir. Adım adım açalım.
Sync ile Future ile Stream karşılaştırması:
Bu tablonun sütunları, fonksiyonlar için Dart'ın yerleşik türlerini temsil eder.
int synchronous() => 0;
Future<int> future() async => 0;
Stream<int> stream() => Stream.value(0);
Değiştirilemez ile Değiştirilebilir:
Varsayılan olarak provider'lar widget'lar tarafından değiştirilemez. Provider'ların "Notifier"
çeşidi onları dışarıdan değiştirilebilir kılar.
Bu, özel (private) bir setter'a benzer ("değiştirilemez" provider'lar)
// _state içeriden değiştirilebilir
// ancak dışarıdan değiştirilemez
var _state = 0;
int get state => _state;
Buna karşılık açık (public) bir setter ("değiştirilebilir" provider'lar)
// Her şey "state"i değiştirebilir
var state = 0;
Değiştirilemez ile değiştirilebilir ayrımını, ilke olarak sırasıyla StatelessWidget ile StatefulWidget gibi de düşünebilirsiniz.
Bu tamamen doğru değildir; çünkü provider'lar widget değildir ve her iki tür de "durum" saklar. Ancak ilke benzerdir: "Tek nesne, değişmez" ile "İki nesne, değiştirilebilir".
Provider oluşturmak
Provider'lar "en üst düzey" bildirimler olarak oluşturulmalıdır.
Bu, herhangi bir sınıfın veya fonksiyonun dışında bildirilmeleri gerektiği anlamına gelir.
Bir provider oluşturmanın sözdizimi, yukarıdaki tabloya göre onun "değiştirilebilir" mi yoksa "değiştirilemez" mi olduğuna bağlıdır.
- Değiştirilemez (fonksiyonel)
- Değiştirilebilir (notifier)
- riverpod
- riverpod_generator
final name = SomeProvider.someModifier<Result>((ref) { <your logic here> });
| Provider değişkeni | Bu değişken, provider'ımızla etkileşime girmek için kullanılacak olan şeydir. NOT Provider'ların global oluşundan korkmayın. Provider'lar tamamen değişmezdir. Bir provider bildirmek, bir fonksiyon bildirmekten farklı değildir; ayrıca provider'lar test edilebilir ve bakımı kolaydır. |
| Provider türü | Genellikle Provider, FutureProvider veya StreamProvider'dan biri.
ipucu "Hangi provider'ı seçmeliyim" diye düşünmeyin. Bunun yerine "Ne döndürmek istiyorum" diye düşünün. Provider türü doğal olarak arkasından gelecektir. |
| Değiştiriciler (isteğe bağlı) | Çoğu zaman provider türünden sonra bir "değiştirici" görebilirsiniz. Şu anda iki değiştirici mevcuttur:
|
| Ref | Diğer provider'larla etkileşime girmek için kullanılan bir nesne. |
| Provider fonksiyonu | Provider'larımızın mantığını buraya yerleştiririz.
Bu fonksiyon, provider ilk okunduğunda çağrılır. |
@riverpod Result myFunction(Ref ref) { <your logic here> }
| Anotasyon | Üretilen tüm provider'lar Örneğin, |
| İşaretlenmiş fonksiyon | İşaretlenmiş fonksiyonun adı, provider ile nasıl etkileşime girileceğini
belirler. İşaretlenmiş fonksiyonlar ilk parametre olarak mutlaka bir Ref belirtmelidir. Bu fonksiyon, provider ilk okunduğunda çağrılır. |
| Ref | Diğer provider'larla etkileşime girmek için kullanılan bir nesne. |
- riverpod
- riverpod_generator
final name = SomeNotifierProvider.someModifier<MyNotifier, Result>(MyNotifier.new); class MyNotifier extends SomeNotifier<Result> { @override Result build() { <your logic here> } <your methods here> }
| Provider değişkeni | Bu değişken, provider'ımızla etkileşime girmek için kullanılacak olan şeydir. NOT Provider'ların global oluşundan korkmayın. Provider'lar tamamen değişmezdir. Bir provider bildirmek, bir fonksiyon bildirmekten farklı değildir; ayrıca provider'lar test edilebilir ve bakımı kolaydır. |
| Provider türü | Genellikle NotifierProvider, AsyncNotifierProvider veya StreamNotifierProvider'dan biri. AsyncNotifierProvider, en çok kullanmak isteyeceğiniz türdür. ipucu Fonksiyonel provider'larda olduğu gibi, "Hangi provider'ı seçmeliyim" diye düşünmeyin. Hangi durumu oluşturmak istiyorsanız onu oluşturun; provider türü doğal olarak arkasından gelecektir. |
| Değiştiriciler (isteğe bağlı) | Çoğu zaman provider türünden sonra bir "değiştirici" görebilirsiniz. Şu anda iki değiştirici mevcuttur:
|
| Notifier'ın kurucusu | "Notifier provider"ların parametresi, "notifier"ı örneklemesi
beklenen bir fonksiyondur. |
| Notifier |
Bu sınıf, provider'ın durumunu değiştirmenin yollarını sunmaktan sorumludur. DİKKAT Notifier'ınızın kurucusuna mantık koymayın. |
| Notifier türü | Notifier'ınızın türetildiği temel sınıf, provider'ınkiyle (ve kullanılıyorsa "family" ile) eşleşmelidir. Bazı örnekler:
|
| build metodu | Tüm notifier'lar Bu metot doğrudan çağrılmamalıdır. |
@riverpod class MyNotifier extends _$MyNotifier { @override Result build() { <your logic here> } <your methods here> }
| Anotasyon | Tüm provider'lar Örneğin, |
| Notifier | Bir Notifier'lar, provider'ın durumunu değiştirmenin yollarını sunmaktan sorumludur. DİKKAT Notifier'ınızın kurucusuna mantık koymayın. |
| build metodu | Tüm notifier'lar Bu metot doğrudan çağrılmamalıdır. |
Herhangi bir sınırlama olmaksızın istediğiniz kadar provider bildirebilirsiniz.
package:provider kullanımının aksine, Riverpod aynı "türde" bir durum sunan
birden fazla provider oluşturmanıza izin verir:
- riverpod
- riverpod_generator
final cityProvider = Provider((ref) => 'London');
final countryProvider = Provider((ref) => 'England');
String city(Ref ref) => 'London';
String country(Ref ref) => 'England';
Her iki provider'ın da bir String oluşturuyor olması hiçbir soruna yol açmaz.
Provider'ları kullanmak
Provider'lar, kendi başlarına hiçbir şey yapmamaları bakımından widget'lara benzer.
Widget'ların kullanıcı arayüzünün bir tarifi olması gibi, provider'lar da durumun
bir tarifidir.
Şaşırtıcı biçimde bir provider tamamen durumsuzdur ve sözdizimini biraz daha
uzatacak olmasa const olarak örneklenebilirdi.
Bir provider'ı kullanmak için ayrı bir nesneye ihtiyacınız vardır: ProviderContainer. Daha fazla bilgi için ProviderContainers/ProviderScopes sayfasına bakın.
Kısacası, bir provider'ı kullanabilmeniz için önce Flutter uygulamalarınızı bir ProviderScope ile sarmalayın:
void main() {
runApp(ProviderScope(child: MyApp()));
}
Bu yapıldıktan sonra, provider'larınızla etkileşime girmek için bir Ref elde etmeniz gerekecek. Bunlar hakkında bilgi için Ref'ler sayfasına bakın.
Kısaca, bir Ref elde etmenin iki yolu vardır:
- Provider'lar doğal olarak bir tanesine erişir.
Bu, provider fonksiyonunun ilk parametresi ya da bir Notifier'ınrefözelliğidir. Bu, provider'ların birbirleriyle iletişim kurmasını sağlar. - Widget ağacı ise Consumer'lar adı verilen özel bir widget türüne ihtiyaç duyar. Bu widget'lar size bir WidgetRef vererek widget ağacı ile provider ağacı arasındaki boşluğu doldurur.
Örnek olarak, basit bir string döndüren bir helloWorldProvider düşünün.
Onu widget'ların içinde şöyle kullanabilirsiniz:
class Example extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, _) {
// Provider'ın değerini elde et
final helloWorld = ref.watch(helloWorldProvider);
// Değeri kullanıcı arayüzünde kullan
return Text(helloWorld);
},
);
}
}