Flutter’da State Management Labirenti
Merhaba değerli okurlar, bu yazımda sizlere bir çok geliştiricinin kafasını kurcalayan acaba hangi state management yöntemi en iyi ya da hangisini kullanmalıyım sorunu yanıtlamaya çalışacağım
Provider, riverpod, bloc, getx, mobx ve diğerleri.. Konuya tam olarak geçmeden önce State nedir önce onu öğrenelim
State Nedir?
En basit tanımıyla state, uygulamanızın herhangi bir anındaki verisidir. Kullanıcının gördüğü ve etkileşime girdiği her şey, state’in bir yansımasıdır.
- Bir sayaç uygulamasındaki sayının değeri (int count = 0;)
- Kullanıcının giriş yapıp yapmadığı bilgisi (bool isLoggedIn = false;)
- Alışveriş sepetindeki ürünlerin listesi (List<Product> cartItems;)
- Bir formdaki metin alanının içeriği (TextEditingController)
- Uygulamanın açık mı yoksa koyu modda mı olduğu
Kullanıcı butona bastığında, bir API’den veri geldiğinde veya bir animasyon çalıştığında, bu “state” değişir. Flutter’ın görevi ise bu state değişikliğini algılayıp kullanıcı arayüzünü (UI) güncellemektir.
State’i daha iyi anlamak için Flutter’ın felsefesini kavramamız gerekir. Flutter deklaratif (declarative) bir UI kütüphanesidir. Bu ne anlama gelir?
- İmperatif (Emir Kipi) Yaklaşım (Eski Yöntem): “Hey metin kutusu, git ve içindeki yazıyı ‘Merhaba Dünya’ olarak değiştir. Sonra rengini mavi yap.” Bu yaklaşımda, UI elemanlarını tek tek bulup manuel olarak değiştirirsiniz. (Ör: Android XML’de findViewById ve sonra textView.setText(…))
- Deklaratif (Bildirimsel) Yaklaşım (Flutter’ın Yolu): “Benim UI’ım, elimdeki isim değişkeni ‘Dünya’ ise, ekranda mavi renkli bir ‘Merhaba Dünya’ metni göstermelidir.” Siz UI’ı doğrudan değiştirmezsiniz. Siz, elinizdeki state’e göre UI’ın nasıl görünmesi gerektiğini tarif edersiniz. State değiştiğinde, Flutter bu tarifi alıp UI’ı sıfırdan, olması gereken hale kendisi getirir.
İşte bu yüzden state, Flutter evreninin merkezidir. Sizin göreviniz widget’ları değiştirmek değil, state’i yönetmektir. State değiştiğinde, Flutter gerisini halleder.
Betimlemek gerekirse Mimar ve İnşaat ekibini düşünelim;
- State: Mimarın çizdiği proje (blueprint). Proje, binanın tüm detaylarını içerir: kaç oda olacağı, duvarların rengi, pencerelerin yeri vb.
- Widget’lar: Sizin yazdığınız kod, yani projenin kendisi. build metodu, mimarın inşaat ekibine verdiği talimattır: “Elimdeki projeye bakarak bir bina inşa et.”
- Flutter Engine: İnşaat ekibi. Bu ekip projeyi (widget’larınızı) okur ve ekrana gerçek binayı (pikselleri) çizer.
Şimdi, mimar projede bir değişiklik yapmak istediğinde (örneğin, bir duvarın rengini kırmızıdan maviye çevirmek istediğinde), ne yapar? Gidip mevcut duvara fırçayla mavi boya sürmez. Projenin yeni bir versiyonunu çizer ve inşaat ekibine verir: “Eski projeyi unutun, işte yeni proje bu. Bu projeye göre binayı güncelleyin.”
Flutter da tam olarak bunu yapar. setState() çağırdığınızda, Flutter’a şunu dersiniz: “Benim state’im (projem) değişti. Lütfen build metodunu (talimatları) tekrar çalıştır ve UI’ı (binayı) bu yeni state’e göre yeniden çiz.”
Yani State, uygulamanızın DNA’sıdır. UI, bu DNA’nın sadece bir yansımasıdır.
State Türleri
State management paketlerine dalmadan önce, temel state türünü ayırt etmek kritik öneme sahiptir. Bu ayrımı anlamak, gereksiz karmaşıklıktan kaçınmanın anahtarıdır.
Ephemeral State (Geçici / Yerel State)
Bu state, “başkalarını ilgilendirmeyen kişisel bir not” gibidir.
- Bir evin yatak odasındaki ışık düğmesini düşünün. Bu düğmenin durumu (açık/kapalı) sadece ve sadece o odayı ilgilendirir. Salonda oturan birinin, yatak odasındaki ışığın durumunu bilmesine veya değiştirmesine gerek yoktur.
- Bu durumu yönetmek için tüm evin elektrik planını (App State) değiştirmek aşırıya kaçmak olur. Odadaki basit bir anahtar (StatefulWidget) bu iş için yeterlidir.
Teknik Açıdan Neden StatefulWidget İdealdir?
- Kapsülleme (Encapsulation): StatefulWidget içindeki state (örneğin _currentIndex) genellikle private’dır (_ öneki ile). Bu, state’in dışarıdan yanlışlıkla değiştirilmesini engeller. Kontrol tamamen o widget’ın elindedir.
- Verimlilik: setState() çağrıldığında, Flutter tüm uygulamayı yeniden çizmez. Sadece o StatefulWidget’ın alt ağacını “kirli” (dirty) olarak işaretler ve verimli bir şekilde sadece değişen kısımları günceller. Yerel bir iş için en performanslı çözüm budur.
- Basitlik: Harici bir kütüphaneye, karmaşık konseptlere veya ekstra dosyalara gerek yoktur. Flutter’ın temel yapı taşıyla işinizi çözersiniz.
Ne Zaman Kullanılır: Checkbox’ın tikli olup olmadığı, bir form alanının odaklanıp odaklanmadığı, bir Slider’ın anlık değeri, bir animasyonun ilerleyişi gibi sadece o widget’ı ve belki doğrudan altındaki birkaç çocuğu ilgilendiren her durum.
App State (Uygulama / Paylaşılan State)
Bu state, “apartman duyuru panosuna asılan bir ilan” gibidir. Tüm sakinleri (uygulamanın farklı kısımlarını) ilgilendirir.
- Apartmanın ortak Wi-Fi şifresi değiştiğinde, bu bilgiye tüm dairelerin erişebilmesi gerekir. Şifre, tek bir daireye özel değildir.
- Bu yeni şifreyi her dairenin kapısını tek tek çalıp söylemek (“prop drilling”) çok zahmetlidir. Hele ki 50 katlı bir gökdelende! En mantıklısı, bu bilgiyi herkesin kolayca erişebileceği merkezi bir yere (girişteki duyuru panosu) asmaktır.
Teknik Açıdan Yarattığı Problem: “Prop Drilling” (Veri Sondajı)
- Diyelim ki bir e-ticaret uygulamanız var. Kullanıcının sepetindeki ürün sayısını hem AppBar’da, hem de HomePage’deki bir widget’ta göstermek istiyorsunuz.
- Bu “sepet sayısı” verisi en tepedeki MyApp widget’ında tutuluyorsa, bu veriyi AppBar’a ve HomePage’e ulaştırmak için aradaki tüm widget’ların constructor’ları aracılığıyla bu veriyi bir alt seviyeye aktarması gerekir.
- Bu, aradaki Scaffold, Center, Column gibi widget’ların bu veriyle hiç işi olmasa bile onu taşımak zorunda kalması demektir. Bu, kodunuzu kırılgan, okunması zor ve bakımı imkansız hale getirir. Yeni bir widget eklediğinizde tüm bu veri aktarım zincirini yeniden düzenlemeniz gerekebilir.
İşte State Management paketleri bu “duyuru panosu” görevini üstlenir.
Buraya kadar state kavramını öğrendik şimdi gelelim bu kavrama neden ihtiyaç duyduğumuza neden statefull widget ile yetinemediğimize
3. State Management Nedir ve Neden Gerekli?
State Management, bu “duyuru panosunu” oluşturma, organize etme ve yönetme sanatıdır.
Temel Amacı App State’i (paylaşılan veriyi) widget ağacının dışına çıkarmak ve ihtiyaç duyan herhangi bir widget’ın bu veriye, aradaki widget’ları rahatsız etmeden, doğrudan erişmesini sağlamaktır.
Neden Gerekli
1- Sorumlulukların Tam Ayrışması (Strict Separation of Concerns):
- Düşünün ki bir AuthRepository sınıfınız var. Bu sınıf, Firebase veya kendi sunucunuzla konuşarak kullanıcı girişi, çıkışı gibi işlemleri yapar. Bu, saf bir iş mantığıdır (business logic). İçinde hiçbir UI kodu yoktur.
- Bir AuthViewModel veya AuthBloc sınıfınız var. Bu sınıf, AuthRepository’yi kullanır ve UI’ın ihtiyaç duyduğu state’i (örneğin isLoading, userModel, errorMessage) tutar. Bu da UI’dan bağımsız bir mantık katmanıdır.
- UI katmanınız (widget’larınız) ise sadece bu AuthViewModel’i dinler. isLoading true ise bir CircularProgressIndicator gösterir, userModel dolu ise ProfileScreen’e yönlendirir.
- Sonuç: AuthRepository’nizi UI’dan tamamen habersiz bir şekilde tek başına test edebilirsiniz. Bu, sağlam ve test edilebilir uygulamalar yazmanın temelidir.
2- Hassas Performans Kontrolü:
- İyi bir state management çözümü, size “seçici dinleme” (selective listening) imkanı sunar.
- Örnek: Bir User modeliniz olduğunu düşünün: class User { String name; String profileImageUrl; }.
- AppBar’daki widget sadece kullanıcının name’ini gösteriyor. Profil sayfasındaki Avatar widget’ı ise sadece profileImageUrl’u kullanıyor.
- Kullanıcı adını güncellediğinde, sadece AppBar’daki widget’ın yeniden çizilmesi gerekir. Avatar widget’ının yeniden çizilmesi gereksiz bir işlemdir.
- Provider’daki Selector veya Riverpod’daki select gibi mekanizmalar, bir widget’ın state’in tamamını değil, sadece ihtiyaç duyduğu küçük bir parçasını dinlemesini sağlar. State’in alakasız bir kısmı değiştiğinde, widget’ınız gereksiz yere yeniden inşa edilmez. Bu, büyük uygulamalarda ciddi performans kazanımları sağlar.
3- Öngörülebilir State Akışı ve Hata Ayıklama:
- State değişiklikleri rastgele yerlerde (setState) değil, merkezi ve belirli noktalarda (örneğin bir BLoC’a event göndererek veya bir Notifier’daki bir metodu çağırarak) gerçekleştiğinde, uygulamanızın akışını takip etmek çok daha kolaylaşır.
- Bir hata olduğunda, sorunun nerede olduğunu tahmin etmek yerine, state’in hangi olaydan sonra bozulduğunu net bir şekilde görebilirsiniz.
- flutter_bloc gibi kütüphaneler, her event ve state geçişini konsola yazdıran BlocObserver gibi harika hata ayıklama araçları sunar. Bu sayede uygulamanızın “zaman içindeki yolculuğunu” izleyebilirsiniz.
Kısacası, state management bir lüks veya sadece büyük uygulamalar için bir gereklilik değildir. Flutter’ın deklaratif doğasının getirdiği sorunlara zarif, ölçeklenebilir ve performanslı bir çözümdür. Bu temelleri sağlam bir şekilde anladığınızda, paketlerin sadece bu hedeflere ulaşmak için farklı yollar sunan araçlar olduğunu göreceksiniz.
Peki, eyvAllah state management gerekli bir şey anladık ama o kadar paket arasından hangisi iyi, hangisini seçeceğiz…
Doğru Paketi Seçmek
En iyi paket yoktur, “projeniz için en uygun” paket vardır. Seçim yaparken kendinize şu soruları sorun:
1. Projenin Büyüklüğü ve Karmaşıklığı:
- Küçük/Kişisel Projeler: Basit bir sayaç veya yapılacaklar listesi gibi uygulamalar için Provider veya Riverpod’ın temel kullanımı fazlasıyla yeterlidir.
- Orta/Büyük Ölçekli Projeler: Birden fazla geliştiricinin çalıştığı, karmaşık iş akışları olan, test edilebilirliğin kritik olduğu projelerde Riverpod veya BLoC gibi daha yapısal çözümler öne çıkar.
2. Öğrenme Eğrisi:
- Kolay: Provider. Flutter’ın InheritedWidget’ının üzerine kurulduğu için Flutter’a en yakın hissettiren pakettir. Konseptleri anlamak çok kolaydır.
- Orta: Riverpod, GetX. Riverpod, Provider’ın gelişmiş halidir ama kendine has yeni konseptler (compile-time safety, ref objesi) getirir. GetX ise kendi ekosistemini yarattığı için öğrenmesi biraz zaman alabilir.
- Zor: BLoC. Stream’ler, event’ler, state’ler gibi reaktif programlama konseptlerine hakimiyet gerektirir. En dik öğrenme eğrisine sahiptir ama ustalaştığınızda çok güçlü bir araçtır.
3. Boilerplate Kodu (Tekrar Eden Kod):
- Az: GetX ve kod üretimi (code generation) kullanan Riverpod en az boilerplate’e sahip olanlardır.
- Orta: Provider.
- Çok: BLoC. Özellikle flutter_bloc kütüphanesi olmadan saf BLoC uygulamak, event, state ve bloc sınıfları yüzünden oldukça fazla kod yazmayı gerektirebilir. (Ancak bu “kötü” bir şey değildir, bu yapı size disiplin ve netlik kazandırır.)
4. Test Edilebilirlik:
- Eğer projenizde unit ve widget testleri yazmak önceliğiniz ise iş mantığını UI’dan tamamen soyutlayan paketler en iyisidir.
- Mükemmel: BLoC ve Riverpod, tasarımları gereği test edilebilirliğe çok önem verirler. Bağımlılıkları enjekte etmek (dependency injection) ve mantık sınıflarını tek başlarına test etmek çok kolaydır.
5. Topluluk Desteği ve Dokümantasyon:
- Neyse ki, Provider, Riverpod, BLoC ve GetX gibi popüler çözümlerin hepsi devasa topluluklara, harika dokümantasyonlara ve bolca eğitim materyaline sahiptir. pub.dev sitesinden paketin popülerliğini, son güncelleme tarihini ve açık issue sayılarını kontrol etmek iyi bir fikirdir.
Paketler Hakkında Kendi Yorumlarım
- Provider: Flutter ekibi tarafından bir dönem “tercih edilen yaklaşım” olarak önerildi. Basit, etkili ve Flutter’ın kendi yapısına çok uygun. Yeni başlayanlar için harika bir başlangıç noktası.
- Riverpod: Provider’ın yazarı tarafından, Provider’ın bazı kısıtlamalarını (örneğin BuildContext bağımlılığı, runtime hataları) çözmek için geliştirildi. Compile-time safety (derleme zamanı güvenliği) sunar, daha esnek ve test edilebilirdir. Benim kişisel olarak yeni başlayacağım tüm projeler için varsayılan tercihim Riverpod’dır.
- BLoC / flutter_bloc: Özellikle büyük, karmaşık ve event-driven (olay güdümlü) uygulamalar için bir güç merkezidir. Kullanıcı etkileşimlerini “olay” olarak ele alır ve bunlara karşılık yeni “state”ler üretir. Büyük ekipler ve uzun ömürlü kurumsal projeler için mükemmel bir disiplin ve ölçeklenebilirlik sunar.
- GetX: Sadece bir state management paketi değil, aynı zamanda bir mikro framework’tür (routing, dependency injection vb.). Çok hızlı ve az kod gerektirmesiyle bilinir. Ancak Flutter’ın standart widget yapısından ve BuildContext’ten uzaklaşması, “fazla sihirli” olması ve kendi ekosistemine sizi hapsetmesi nedeniyle toplulukta tartışmalı bir konuma sahiptir. Hızlı prototipleme için cazip olabilir, ancak büyük ve uzun soluklu projelerde dikkatli kullanılmalıdır.
Flutter’da state management için “en iyi” çözüm diye bir şey yoktur. Her paketin güçlü ve zayıf yönleri, hitap ettiği bir proje ölçeği ve geliştirici profili vardır.
Size son tavsiyem şu şekilde olurdu:
- Flutter’a yeni başlıyorsanız: Kesinlikle StatefulWidget’ı anlayarak başlayın, ardından Provider’ı öğrenin. Bu size sağlam bir temel verecektir.
- Yeni ve potansiyel olarak büyüyecek bir projeye başlıyorsanız: Gözünüz kapalı Riverpod’ı seçebilirsiniz. Modern, güvenli ve ölçeklenebilir bir çözümdür.
- Büyük bir ekiple kurumsal bir uygulama geliştiriyorsanız: Riverpod veya BLoC arasındaki seçimi ekibinizin tecrübesine ve projenin gereksinimlerine göre yapın. BLoC’un getirdiği katı yapı, büyük ekiplerde tutarlılığı sağlamak için çok değerli olabilir.
Bir makalenin daha sonuna geldik. Unutmayın, en iyi araç, sizin ve ekibinizin en verimli şekilde kullanabildiği, projenizin ihtiyaçlarını en iyi karşılayan araçtır. Farklı yaklaşımları küçük projelerde deneyerek kendiniz için en uygun olanı keşfedebilirsiniz.
Okuyan ve destekleyen tüm okurlara teşekkür ederim. İyi çalışmalar, temiz ve güvenli kodlamalar dilerim… Umarım bu makale meraklısı ve ilgilisi için faydalı olur
Sonraki makalelerde görüşmek üzere..
Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow: https://stackoverflow.com/users/13807726/abdullah-t