Ana içeriğe atla

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:

// 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);
});

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:

SenkronFutureStream
DeğiştirilemezProviderFutureProviderStreamProvider
DeğiştirilebilirNotifierProviderAsyncNotifierProviderStreamNotifierProvider

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;
bilgi

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.

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.
Değişken final ve "en üst düzey" (global) olmalıdır.

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.
Kullanılan provider türü, fonksiyonunuzun döndürdüğü değere bağlıdır. Örneğin bir Future<Activity> oluşturmak için bir FutureProvider<Activity> istersiniz.

FutureProvider, en çok kullanmak isteyeceğiniz türdür.

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.
Değiştiriciler isteğe bağlıdır ve provider'ın davranışını tür güvenli bir biçimde ayarlamak için kullanılır.

Şu anda iki değiştirici mevcuttur:

  • autoDispose, provider kullanılmayı bıraktığında önbelleği otomatik olarak temizler.
    Ayrıca bkz. Otomatik yok etme
  • family, provider'ınıza argüman geçirmenizi sağlar.
    Ayrıca bkz. Family.
Ref

Diğer provider'larla etkileşime girmek için kullanılan bir nesne.
Tüm provider'ların bir tane vardır; ya provider fonksiyonunun parametresi olarak, ya da bir Notifier'ın özelliği olarak.

Provider fonksiyonu

Provider'larımızın mantığını buraya yerleştiririz. Bu fonksiyon, provider ilk okunduğunda çağrılır.
Sonraki okumalar fonksiyonu yeniden çağırmaz, bunun yerine önbelleğe alınmış değeri döndürür.

bilgi

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:

final cityProvider = Provider((ref) => 'London');
final countryProvider = Provider((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'ın ref ö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);
},
);
}
}