Bu proje, Flutter'da state yönetimi ve kod yapısını iyileştirmek için yapılan bir refactor (yeniden düzenleme) sürecini göstermektedir. Proje, basit bir hava durumu uygulaması üzerinden StatefulWidget ve setState kullanımından, modern ve güçlü bir state management kütüphanesi olan Riverpod'a geçişi adım adım sergilemektedir.
https://medium.com/@abdullahtas/flutter-dart-projelerinde-refactoring-94b2a9598915 https://medium.com/@abdullahtas/flutter-refactoring-prati%C4%9Fi-00c10749fba4
Bu döküman, projenin amacını, yapılan değişiklikleri, kullanılan teknolojileri ve projenin nasıl çalıştırılacağını detaylı bir şekilde açıklamaktadır.
Projenin temel amacı, Flutter uygulamalarında sürdürülebilir, test edilebilir ve ölçeklenebilir bir kod yapısının nasıl oluşturulacağını göstermektir. Bu amaçla, aşağıdaki konulara odaklanılmıştır:
- State Yönetimi:
setState'in getirdiği zorluklardan kaçınarak,Riverpodile state'i merkezi ve reaktif bir şekilde yönetmek. - Kod Yapısı: Kodun daha modüler ve okunabilir hale getirilmesi için sorumlulukların ayrılması (Separation of Concerns).
- Immutable (Değişmez) Modeller:
freezedpaketi ile veri modellerini değişmez ve güvenli hale getirmek. - Test Edilebilirlik: Yazılan kodun birim testler (unit tests) ile kolayca test edilebilir olmasını sağlamak.
Proje, refactor öncesi ve sonrası durumu net bir şekilde görebilmeniz için lib klasörü altında before ve after olarak ikiye ayrılmıştır.
lib/
├── after/ # Refactor edilmiş modern kod yapısı
│ ├── data/
│ │ └── weather_repository.dart
│ ├── models/
│ │ └── weather_models.dart
│ ├── providers/
│ │ └── weather_provider.dart
│ └── after_weather_page.dart
├── before/ # Başlangıçtaki StatefulWidget kodu
│ └── before_weather_page.dart
└── main.dart # Uygulamanın başlangıç noktası
Bu klasördeki before_weather_page.dart dosyası, tüm mantığın tek bir StatefulWidget içinde olduğu, setState ile state yönetiminin yapıldığı başlangıç noktasını temsil eder. Bu yaklaşım, küçük uygulamalar için hızlı bir başlangıç sunsa da, uygulama büyüdükçe şu gibi sorunlara yol açar:
- State Dağınıklığı: State, UI kodunun içinde kaybolur ve yönetimi zorlaşır.
- Test Zorluğu: UI ve iş mantığı iç içe olduğu için test yazmak karmaşıklaşır.
- Gereksiz Rebuild'ler:
setStateçağrıldığında tüm widget ağacının yeniden çizilmesi performans sorunlarına neden olabilir.
Bu klasör, Riverpod ve diğer modern pratikler kullanılarak yeniden düzenlenmiş kodu içerir. Kod, sorumluluklarına göre aşağıdaki gibi ayrılmıştır:
data/weather_repository.dart: API istekleri gibi veri kaynaklarıyla ilgili tüm mantığı içerir. Bu sayede, veri getirme işlemleri UI katmanından tamamen soyutlanmıştır.models/weather_models.dart:freezedpaketi kullanılarak oluşturulmuş, API'den gelen veriyi temsil eden değişmez (immutable) veri modelini içerir.providers/weather_provider.dart:Riverpodprovider'larını tanımlar. Bu provider'lar,WeatherRepository'yi kullanarak veriyi çeker ve state'i yönetir.after_weather_page.dart: UI katmanıdır.ConsumerWidgetkullanarak provider'ları dinler ve state'deki değişikliklere göre UI'ı günceller. İş mantığından arındırılmış, sadece UI çiziminden sorumlu bir yapıdadır.
Riverpod, Flutter uygulamaları için modern, derleme zamanında güvenli (compile-safe) ve test edilebilir bir state management çözümüdür. Bu projede Riverpod'ı şu amaçlarla kullandık:
- Dependency Injection (Bağımlılık Enjeksiyonu):
WeatherRepositorygibi bağımlılıkları provider'lar aracılığıyla UI katmanına enjekte ederek, kodun modüler ve test edilebilir olmasını sağladık. - Reaktif State Yönetimi:
FutureProviderkullanarak asenkron veri getirme işlemlerini (API istekleri gibi) kolayca yönettik.FutureProvider,loading,dataveerrorgibi durumları otomatik olarak ele alarak UI'da bu durumları güvenli bir şekilde işlememizi sağlar. - Performans: Sadece state'i dinleyen widget'ların yeniden çizilmesini sağlayarak gereksiz rebuild'lerin önüne geçtik.
freezed, Dart için güçlü bir kod üreticisidir. Veri modellerini (data classes) değişmez (immutable) hale getirir ve copyWith, == operatorü, toString gibi standart metodları otomatik olarak oluşturur. Bu projede freezed'i şu avantajları için kullandık:
- Güvenilirlik: Değişmez nesneler, state'in beklenmedik şekillerde değişmesini engelleyerek hataları azaltır.
- Kod Tekrarını Önleme: Boilerplate kodları (standart, tekrarlayan kodlar) otomatik olarak üreterek geliştirme sürecini hızlandırır.
- JSON Serileştirme:
json_serializableile entegre çalışarak, API'den gelen JSON verisini kolayca Dart nesnelerine dönüştürmemizi sağlar.
Refactor sürecinin en önemli hedeflerinden biri de test edilebilirliği artırmaktır. Riverpod ve bağımlılık enjeksiyonu sayesinde, iş mantığını UI'dan ayırarak test yazmayı kolaylaştırdık.
test klasörü altında bulunan weather_provider_test.dart dosyası, weatherProvider'ın davranışını test eder. Bu testte:
mockito:WeatherRepository'nin sahte (mock) bir versiyonunu oluşturmak için kullanılır. Bu sayede, testimiz gerçek API'ye istek atmadan, kontrolümüz altındaki sahte verilerle çalışır.ProviderContainer:Riverpodprovider'larını bir test ortamında çalıştırmak ve state'lerini okumak için kullanılır.- Test Senaryoları: Provider'ın başarı (
data), yükleniyor (loading) ve hata (error) durumlarını doğru bir şekilde yönetip yönetmediği test edilir.
Projeyi yerel makinenizde çalıştırmak için aşağıdaki adımları izleyin:
-
Projeyi Klonlayın:
git clone <proje-linki> cd flutter_refactor_example
-
Bağımlılıkları Yükleyin:
flutter pub get
-
Kod Üretimini Çalıştırın: Proje,
freezedvemockitogibi kod üreticileri kullandığı için, gerekli dosyaları oluşturmak üzere aşağıdaki komutu çalıştırmanız gerekmektedir. Bu komut,*.g.dartve*.freezed.dartgibi dosyaları üretecektir.flutter pub run build_runner build --delete-conflicting-outputs
-
Uygulamayı Başlatın:
flutter run
Projenin birim testlerini çalıştırmak için aşağıdaki komutu kullanabilirsiniz:
flutter testBu komut, test klasöründeki tüm testleri çalıştıracak ve sonuçları konsolda gösterecektir.