Flutter'da state yönetimi: setState'ten Riverpod ve BLoC'a
Flutter uygulamasında state'i kademeli yönetmek: setState'ten Riverpod provider ve BLoC Cubit kullanımına, örnek kod, seçim ölçütleri ve sık hatalar.
State nedir, neden yönetmek gerekir?
Flutter’da arayüz, uygulamanın o anki verisinin bir fonksiyonudur. Bu veriye state denir: sepetteki ürünler, oturum açmış kullanıcı, bir formun doğrulama durumu, sunucudan gelen liste. State değiştiğinde ilgili widget’ların yeniden çizilmesi gerekir.
Flutter belgeleri state yönetiminde farklı yaklaşımları sıralar. Widget’a özgü, geçici durum için en alt düzey çözüm setState’tir. Yalnızca Flutter’ın kendi API’leriyle çalışan ValueNotifier ve InheritedNotifier gibi seçenekler vardır. InheritedWidget ise ağaçta üst öğeden alt öğeye veri taşıyan düşük düzey mekanizmadır ve birçok paket onu temel alır. Belgeler, uygulamanın karmaşıklığına ve ekibin tercihine göre bir state yönetimi paketi kullanmanın boilerplate kodu azaltabileceğini, hata ayıklama araçları sağlayabileceğini ve tutarlı bir mimari kurulmasına yardım edebileceğini belirtir.
Adım adım: hangi durumda hangi araç?
Pratik bir ilerleme şöyledir:
- Bir widget’ın kendi içinde tuttuğu geçici durum için setState yeterlidir; örneğin bir kutunun açık veya kapalı olması.
- Birkaç widget aynı veriyi paylaşıyorsa bu veriyi ortak üst öğeye ya da bir yönetici sınıfa taşıyın.
- Veri sunucudan geliyor, önbelleğe alınıyor ve birden çok ekranda kullanılıyorsa bir state yönetimi paketi gerekir.
- Ekip büyüdükçe ve iş kuralları karmaşıklaştıkça, durum değişimlerini açıkça tarif eden ve test edilebilir bir yapı tercih edilir.
Bu noktada öne çıkan seçenekler devreye girer. Riverpod, provider denilen önbellekli fonksiyonlar üzerinden veriyi sunar. BLoC kütüphanesi ise Cubit ve Bloc sınıfları ile durum değişimini akış (Stream) olarak modeller. İkisi de ölçeklenebilir uygulamalarda kullanılır; seçim ekibin alışkanlığına ve uygulamanın yapısına göre yapılır.
Riverpod ile state yönetimi
Riverpod belgeleri provider’ı, sonuçları önbelleğe alan bir fonksiyon sarmalayıcısı olarak tanımlar: aynı parametrelerle çağrıldığında önbellekteki değeri döndürür. Böylece birden çok widget aynı veriye erişir ve sonucu elle paylaşmanıza gerek kalmaz. Belgeler ayrıca otomatik kaynak bırakma, FutureBuilder ve StreamBuilder ihtiyacını ortadan kaldıran veri bağlama, hata yönetimi ve test için taklit edebilme gibi özellikleri sayar.
Basit bir örnek: final productsProvider = FutureProvider<List<Product>>((ref) async { return ref.watch(productRepositoryProvider).fetch(); }); Burada productsProvider sunucudan ürün listesini getirir ve sonucu önbellekte tutar. Başka bir provider’ı ref.watch ile izlemek, o provider değiştiğinde bu provider’ın da otomatik yenilenmesini sağlar. Arayüzde bu provider’ı izleyen widget, yüklenme, hata ve veri durumlarını ayrı ayrı yönetir.
Riverpod’un dikkat çeken avantajı, provider’ların widget ağacından bağımsız tanımlanması ve bağımlılıkların açıkça izlenmesidir. Sürüm geçişleri sırasında API değişebildiği için kullandığınız sürümün belgesini esas alın.
BLoC ile state yönetimi: Cubit ve Bloc
BLoC kütüphanesi Dart’ın Stream yapısı üzerine kuruludur. Stream, zaman içinde gelen asenkron verilerin dizisidir. Kütüphanenin en yalın bileşeni Cubit’tir: BlocBase’i genişleten bir sınıftır, durum değişimini tetikleyen fonksiyonlar sunar ve yeni durumu emit ile yayar.
Örnek:
class CartCubit extends Cubit<CartState> { CartCubit() : super(const CartState.empty()); void addProduct(Product product) => emit(state.add(product)); }
Belgelere göre emit metodu korumalıdır, yalnızca Cubit içinde kullanılmalıdır. Arayüz, mevcut durumu state üzerinden okur, değişimleri stream üzerinden dinler. Cubit’in üstünde Bloc vardır: fonksiyon çağırmak yerine olay (event) alır ve olaylardan duruma dönüşümü tanımlar. Bu, olay geçmişini izlemeyi ve daha karmaşık dönüşümleri düzenli ifade etmeyi kolaylaştırır. Her durum değişikliği bir Change nesnesiyle gözlemlenebilir; BlocObserver ile tüm değişiklikler tek yerden izlenebilir.
Riverpod mu BLoC mu? Seçim ölçütleri
Bu yaklaşımların her biri üretimde kullanılan olgun yaklaşımlardır; hangisinin “daha iyi” olduğu uygulamaya ve ekibe bağlıdır. Karar verirken şu soruları sorun:
- Ekip akış tabanlı, olay-durum modeline mi yakın, yoksa önbellekli fonksiyon mantığına mı?
- Uygulamada karmaşık olay dizileri ve iş kuralları var mı, yoksa ağırlıklı olarak sunucu verisini göstermek mi gerekiyor?
- Durum geçişlerinin açıkça kayıt altına alınması ve izlenmesi önemli mi?
- Yeni katılan geliştiricinin öğrenme eğrisi nasıl?
Takım içinde standart belirlemek, tek tek tercihlerden daha önemlidir. Aynı uygulamada farklı yaklaşımları karıştırmak mümkündür ama bakım yükünü artırır; geçiş yapılacaksa modül modül ilerlemek mantıklıdır.
Sık yapılan hatalar ve nasıl önlenir
State’i fazla yukarı taşımak: Her şeyi global yapmak, gereksiz yeniden çizimlere ve karmaşaya yol açar. Yerel durumu yerelde bırakın.
İş mantığını widget içine gömmek: Hesaplama, doğrulama ve sunucu çağrıları Cubit, Bloc ya da provider katmanında kalmalıdır; böylece widget testsiz kalan mantıktan arınır.
Değiştirilebilir nesneleri doğrudan değiştirmek: Durumu yeni bir nesne olarak yayın; var olan listeye ekleme yapıp aynı nesneyi yayarsanız değişiklik algılanmayabilir.
Kaynakları kapatmamak: Cubit ve Stream aboneliklerinin kapatılması gerekir; belgeler cubit.close ve subscription.cancel çağrılarını gösterir.
Hata ve yükleniyor durumlarını atlamak: Her asenkron veri için yüklenme, hata ve boş veri durumlarını açıkça tasarlayın.
Test yazmamak: Bu yapılar, arayüzden bağımsız test edilebilirlikleri sayesinde seçilir; bu avantajı kullanmayan ekip faydanın önemli bir kısmını kaçırır.
Bir hata ayıklama ipucu: durum beklenmedik biçimde değişiyorsa değişikliği gözlemleyin. BLoC tarafında BlocObserver ile her durum geçişi tek yerden günlüklenebilir; Riverpod tarafında belgelerde anlatılan provider gözlemcileri benzer iş görür. Böylece hangi olayın hangi durumu ürettiği izlenir ve hata kaynağı hızla daraltılır.
Bu beceri Mindset programlarında nerede çalışılır?
Flutter ile Mobil Geliştirme eğitimi; Flutter ve Dart temellerinden widget sistemi ve yerleşime, Riverpod ve BLoC ile state yönetimine, navigasyona, ağ ve veri kalıcılığına, animasyon ve performansa, test ve mağaza dağıtımına uzanan bir akış sunuyor. State yönetimi bölümü, widget sisteminden sonra ve veri katmanından önce konumlanıyor.
Bu eğitim, mobil uygulama geliştirmeye geçen yazılım ekipleri veya mevcut uygulamanın mimarisini düzenlemek isteyen geliştiriciler için uygun görünüyor. Kurum içi mobil ekibinizin kod tabanı ve ihtiyaçlarına göre uyarlanmış bir eğitim için kurumsal eğitim talebi üzerinden görüşebilirsiniz.
İlgili Mindset programı
Flutter ile Mobil Geliştirme
Sık sorulan sorular
Küçük bir uygulamada Riverpod ya da BLoC gerekir mi?
Gerekmeyebilir. Birkaç ekranlı, durumu çoğunlukla yerel kalan bir uygulamada setState ve yerleşik çözümler yeterlidir. Veri paylaşımı, önbellek ve test ihtiyacı arttıkça bir paket kullanmak anlamlı hale gelir. Küçük başlayıp ihtiyaç doğdukça geçmek makul bir yoldur.
Cubit mi Bloc mu kullanmalıyım?
Cubit daha az kod gerektirir ve doğrudan fonksiyon çağırarak durum değiştirir; basit durum yönetiminde yeterlidir. Bloc ise olay tabanlıdır; olay geçmişinin izlenmesi ya da olayların dönüştürülmesi gerektiğinde avantajlıdır. Çoğu ekip Cubit ile başlar, ihtiyaç doğarsa Bloc’a geçer.
Riverpod ve BLoC birlikte kullanılabilir mi?
Teknik olarak evet, ancak aynı uygulamada ayrı modeller öğrenme ve bakım yükünü artırır. Geçiş sürecinde modül bazında birlikte yaşatmak mümkündür. Kalıcı çözüm olarak ekip için tek bir yaklaşımı standart almak daha sürdürülebilirdir.
State yönetimini öğrenirken hangi sırayı izlemeliyim?
Önce Dart ve widget yaşam döngüsünü, sonra setState ve ortak üst öğeye state taşımayı öğrenin. Ardından küçük bir uygulamayı hem Riverpod hem Cubit ile yazıp farkı gözlemleyin. Son olarak sunucu verisi, hata durumları ve test yazımını ekleyin.