Riverpod Nedir? İşte Farklı Kaynaklardan Öğrendiğim En Önemli Bilgiler

 

Herkese merhaba.

Bu ara Flutter’da StateManagement konusuna kafayı takmış durumdayım. Çünkü Flutter konusunda bildiklerim arttıkça daha kapsamlı projeler yapma girişiminde bulunuyorum. Ancak her defasında veri taşıma, saklama, çağırma konusu geldiğinde tıkanıp kalıyorum. Daha önce provider paketini biraz kullanmayı denedim ancak şu an üzerinde çalıştığım uygulamam için provider yeterli olmuyor. Bunun için araştırmalarım Riverpod’un provider’dan daha kapsamlı projelerde kullanılabileceğine işaret edince başladım üzerinde çalışmaya.

Ve şimdi buraya da önce dökümantasyondan sonra da farklı kaynaklardan edindiğim bilgileri derlemeye geldim. Umarım ilham olurum. Haydi başlayalım.

Riverpod nedir?

Riverpod ile ilgili ilk notumun bir çeşit “icebreaker” olmasını umuyorum. Riverpod, provider’ın anagramıymış, aynı harflerin farklı dizilimi :)

Gelelim ne olduğuna. Riverpod, Flutter’da modern ve esnek bir durum yönetimi (state management) çözümü. Geleneksel yaklaşımlara göre daha güvenli, bağımsız ve test edilebilir bir yapı sunuyor ve bizde onunla uygulamamızdaki durumu daha etkili bir şekilde yönetebilir oluyoruz.

Peki neden StateManagement’a ihtiyacımız var? Riverpod’un detaylarına geçmeden önce biraz bundan bahsetmek istiyorum.

Neden Durum Yönetimi(State Management)?

Birçok farklı ekran ve widget içeren bir uygulama yaptığımızı düşünün. Ve bunlardan bazılarının, aynı state’e -productState gibi- erişmesi gerekebilir. Örneğin aşağıdaki ağaç diyagramında Ana Ekran ve Alışveriş Sepeti widget’ının her ikisi de aynı productState’e ihtiyaç duyabilir.

State Ana Ekran widget’ında tanımlandığında, bunu güncellemek için bu widget içinde bir fonksiyon veya state oluşturmamız ve ardından bu fonksiyon veya state’i alışveriş sepeti widget’ına kadar iletmemiz gerekir. Bunu yaptığımızda, widget ağacı bu durum değişikliğini yansıtacak şekilde yeniden oluşturulur. Bu bir aşamaya kadar nispeten basit ve yaygın bir senaryo. Ancak yaygın olan bir başka senaryo da bunun gibi durumu ağacın tepe noktasında tanımladığımız ve daha sonra widget’lar aracılığıyla ihtiyaç duyulan diğer yerlere aktardığımız operasyonu birden fazla yerde kullanmak durumunda kalmak. İkinci senaryo uygulamada daha yaygın hale geldiğinde, biraz dağınık ve bakımı zorlaşan kodlara yol açabiliyor, başıma geldiği için biliyorum :). İşte bu noktada bir durum yönetimi çözümü bizim için işleri basitleştiriyor.

Aşağıdaki diagramda gösterdiğim gibi provider ile fonksiyon ya da state’i bir kez en tepe noktasında tanımladıktan sonra widget ağacında tüm hiyerarşiyi gezmeden istediğimiz yerde çağırabiliyoruz.

Riverpod’un ne olduğu ve State Management’ın neden gerekli olduğunu konuştuğumuza göre riverpod’u kullanmaya başlayabiliriz.

Kurulumu

Öncelikle her paket kullanımında olduğu gibi paketimizi projemize install ediyoruz.

flutter pub add flutter_riverpod

Ve ardından pubspec.yaml dosyasına aşağıdaki gibi güncel versiyonu ne ise onunla ekleniyor.

dependencies:
flutter_riverpod: ^2.5.3

Kullanacağımız sayfaya ise aşağıdaki gibi import ediyoruz.

import 'package:flutter_riverpod/flutter_riverpod.dart';

Kullanımı

Projemizin main metodu içindeki runApp fonksiyonunu ProviderScope ile sarıyoruz. Bu oluşturulan tüm provider’ların state’lerini tutmaya yarar.

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

Bu arada provider yazılımda kelime anlamı olarak bir state’i kapsayan ve bu state’in dinlenmesini sağlayan nesne demek. Ve tüm StateManagement çözümlerinde aynı anlama geliyor ve basitçe aynı mantıkla kullanılıyor.

Bu noktada basit bir provider nesnesi örneği yazmak istiyorum. Her fonksion ya da property tanımlaması gibi provider’ın da bir dönüş türü oluyor. Vereceğim örnekte bu provider bir String döndürüyor.

final helloRiverpodProvider = Provider<String>((ref) {
return 'Hello Riverpod';
});

Bu provider nesnesine sayfanın her yerinden, yazacağımız tüm widget’lar içerisinden ulaşabilmek için global bir değişken olarak tüm metot ve widget’ların dışına yazıyoruz. Şimdi bu tanımladığımız global değişkeni nasıl kullanacağımıza bakalım.

Provider Nesnesinin Widget İçerisinde Kullanımı

1- ConsumerWidget ile

class HelloRiverpodWidget extends ConsumerWidget { 
const HelloRiverpodWidget({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
return Container();
}
}

Burada oluşturduğumuz widget Stateless veya Stateful Widget’tan değil, Consumer Widget’tan extend olur. ConsumerWidget Stateless Widget gibi çalışır. build metodu içerisinde BuildContext parametresinin yanısıra WidgetRef parametresi de bulunmak zorundadır.

ref.watch metoduyla global variable olarak tanımlanan değişkeni okuyup widget içerisinde ayrı bir değişkene atayarak kullanıyoruz.

class HelloRiverpodWidget extends ConsumerWidget {
const HelloRiverpodWidget({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final helloRiverpod = ref.watch(helloRiverpodProvider);
return Text(helloRiverpod);
}
}

2- Consumer ile

ConsumerWidget ile tüm Scaffold’u sarmaladığımızda Provider nesnesinde her değişiklik olduğunda tüm Scaffold yenilenir. Bu da ekstra performans sorunu demek olabilir. Bunun yerine yalnızca Provider nesnesi içeren Widget’ı Consumer ile sarabiliriz.

class HelloRiverpodWidget extends StatelessWidget {
const HelloRiverpodWidget({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer(
builder: (context, ref, child) {
final helloRiverpod = ref.watch(helloRiverpodProvider);
return Text(helloRiverpod);
},
),
);
}
}

Burada consumer oluşturduğumuzda required olan builder’ı iyi yapılandırmak gerekir. Bu sebeple ConsumerWidget’tan daha detaylıdır.

Bunun yerine widget’larımızı yeniden kullanılabilecek şekilde yapılandırır, küçük parçalara ayırırsak ConsumerWidget’ı kullanmaya devam edebilir, rebuild ile ilgili performans sorununu en aza indirebiliriz.

3- ConsumerStatefulWidget ile

ConsumerWidget’ın StatelessWidget’ın karşılığı olduğunu söylemiştim. StatefulWidget kullanmamız gereken durumlarda karşılık olarak ConsumerStatefulWidget’ı kullanabiliriz.

Burada provider nesnesini çağırmak için iki yöntemimiz var. Birincisi initState() içerisinde çağırmak.

 @override
void initState() {
super.initState();
final helloRiverpod = ref.read(helloRiverpodProvider);
print(helloRiverpod);
}

Diğer yöntem ise klasik widget ağacı içerisinde çağırmak.

class HelloRiverpodStful extends ConsumerStatefulWidget {
const HelloRiverpodStful({super.key});

@override
ConsumerState<ConsumerStatefulWidget> createState() => _HelloRiverpodStfulState();
}

class _HelloRiverpodStfulState extends ConsumerState<HelloRiverpodStful> {

@override
Widget build(BuildContext context) {
final helloRiverpod = ref.watch(helloRiverpodProvider);
return Text(helloRiverpod);
}
}

WidgetRef ref’i build metodu içerisinde kullanıyorsak watch() ile, diğer metotlardan birinde kullanıyorsak read() ile çağırırız. Her iki durum da değer döndürüyor. aşağıda bu iki metodun karşılaştırmasına daha detaylı şekilde yer vereceğim.

WidgetRef ref, Consumer veya ConsumerWidget içerisinde bir argüman olarak, ConsumerState içerisinde ise property olarak değerlendirilir.

WidgetRef BuildContext gibi widget’ların provider nesneleri ile etkileşime girmesini sağlayan nesne. BuildContext widget ağacındaki ata widget’lara ulaşmayı sağlar. Theme.of(context) veya MediaQuery.of(context) gibi. WidgetRef ise uygulama içindeki herhangi bir provider’a erişmeyi sağlar. Çünkü tüm Riverpod provider’ları globaldir.

ref.watch() vs ref.read()

Build metodu içerisinde bir provider nesnesinin durumunu gözlemlemek ve değişirse yeniden build etmek için ref.watch kullanıyoruz.

Bir provider nesnesinin durumunu yalnızca bir kez okumak için (initState gibi) ref.read kullanıyoruz.

Bir butonun onPressed callback metodu içinde ref.watch değil ref.read kullanıyoruz.

Bazen bir provider durumu değiştiğinde SnackBar veya alertDialog göstermek isteriz. Bunu build metodu içinde ref.listen metodunu çağırarak yapabiliriz.

ref.listen build çağrıldığında değil, provider nesnesi değiştiğinde bir callback sağlar. Bu yüzden asenkron kodu çalıştırmak için de kullanılabilir.

Makalemde farklı kaynaklardan araştırdığım ve öğrendiğim Riverpod notlarımı aktardım. Sonlandırmadan önce vscode için bir eklentiden bahsetmek istiyorum: Flutter Riverpod Snippets.

Bu eklenti, sık kullanılan Riverpod kod parçacıklarını (snippets) otomatik olarak tamamlayıp kod yazmayı hızlandırıyor. Örneğin, ProviderStateNotifierConsumerWidget gibi yaygın Riverpod yapılarının temel kodlarını hızlıca ekleyebiliyorsunuz, böylece her seferinde sıfırdan yazmak zorunda kalmıyorsunuz. Hataları da minimize etmenize destek oluyor.

Ben Riverpod’u öğrenmeye devam ediyorum, zira genel yazılım dünyası tanımlaması gibi, derya deniz bir konu olduğunu görebiliyorum. İlerleyen zamanlarda yeni öğrendiklerimi de ikinci bir makalede yazarım.

Hepimize hatasız kodlar.

Okuduğunuz için teşekkürler.

Yeni makalelerimden haberdar olmak için abone olursanız memnun olurum.

Selin.

Hiç yorum yok: