↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)

4 dk okuma #flutter
Dizi · 6/61 Flutter Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
  3. Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
  4. Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
  5. Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
  6. Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
  7. Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
  8. Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
  9. Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
  10. Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
  11. Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
  12. Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
  13. Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
  14. Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
  15. Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
  16. Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
  17. Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
  18. Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
  19. Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
  20. Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
  21. Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
  22. Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
  23. Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
  24. Bölüm 6 — Konu 24: Provider Paketi
  25. Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
  26. Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
  27. Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
  28. Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
  29. Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
  30. Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
  31. Bölüm 7 — Konu 31: Deep Linking
  32. Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
  33. Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
  34. Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
  35. Bölüm 8 — Konu 35: Implicit Animasyonlar
  36. Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
  37. Bölüm 8 — Konu 37: Hero Animasyonları
  38. Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
  39. Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
  40. Bölüm 9 — Konu 40: Platform Channels
  41. Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
  42. Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
  43. Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
  44. Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
  45. Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
  46. Bölüm 10 — Konu 46: CI/CD
  47. Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
  48. Bölüm 10 — Konu 48: DevTools Profiling
  49. Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
  50. Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
  51. Bölüm 11 — Konu 51: Supabase Alternatifi
  52. Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
  53. Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
  54. Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
  55. Bölüm 12 — Konu 55: Flutter Web / Desktop
  56. Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
  57. Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
  58. Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
  59. Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
  60. Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
  61. Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
Dizinin sayfası →
İçindekiler 10 başlık
  1. StatelessWidget — "Hiç Değişmeyen" Widget'lar
  2. StatefulWidget — "Zamanla Değişebilen" Widget'lar
  3. İki Sınıflı Yapı — Neden?
  4. setState() — Rebuild Mekanizmasının Kalbi
  5. Rebuild Mekanizması — Adım Adım Ne Oluyor?
  6. setState()'in Kapsamı — Sadece O Widget mı, Tüm Ağaç mı?
  7. Ne Zaman StatelessWidget, Ne Zaman StatefulWidget?
  8. initState() — İlk Bakış (Detaylı Olarak Bölüm 4/5'te)
  9. 🎯 Bu Dersten Çıkarılması Gerekenler
  10. 📝 Ödevler

Bu, Flutter'ın en kritik konularından biri — burada öğrendiklerin, yazacağın hemen her uygulamada karşına çıkacak.

StatelessWidget — "Hiç Değişmeyen" Widget'lar

Bir önceki derste hep StatelessWidget kullandık. Bu, kendi başına hiçbir zaman değişmeyen bir widget türüdür — oluşturulduktan sonra, aldığı veriler (parametreler) hiç değişmez.

dart
class SelamKarti extends StatelessWidget {
  final String isim;

  const SelamKarti({super.key, required this.isim});

  @override
  Widget build(BuildContext context) {
    return Text('Merhaba, $isim!');
  }
}

SelamKarti("Ahmet") oluşturduğunda, bu widget her zaman "Merhaba, Ahmet!" gösterir. Eğer farklı bir isim göstermek istersen, yeni bir SelamKarti widget'ı oluşturman gerekir (örneğin SelamKarti("Ayşe")) — var olan widget'ı "güncelleyemezsin", çünkü Dart Bölüm 2'de öğrendiğimiz gibi, final isim alanı bir kere atanır, değişmez.

StatefulWidget — "Zamanla Değişebilen" Widget'lar

Peki, bir sayaç uygulamasında olduğu gibi, kullanıcı etkileşimiyle değişen bir değer (örneğin tıklama sayısı) göstermen gerekiyorsa? İşte burada StatefulWidget devreye girer.

dart
class SayacWidget extends StatefulWidget {
  const SayacWidget({super.key});

  @override
  State<SayacWidget> createState() => _SayacWidgetState();
}

class _SayacWidgetState extends State<SayacWidget> {
  int _sayac = 0;

  void _arttir() {
    setState(() {
      _sayac++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Sayaç: $_sayac'),
        ElevatedButton(
          onPressed: _arttir,
          child: Text('Artır'),
        ),
      ],
    );
  }
}

Bu, StatelessWidget'tan yapısal olarak çok farklı — burada iki ayrı class var. Şimdi bunu parça parça açalım.

İki Sınıflı Yapı — Neden?

1. SayacWidget (StatefulWidget'tan türeyen):

dart
class SayacWidget extends StatefulWidget {
  const SayacWidget({super.key});

  @override
  State<SayacWidget> createState() => _SayacWidgetState();
}

Bu class'ın kendisi, hâlâ immutable'dır (Bölüm 2 Konu 5'i hatırla — const constructor, final alanlar olurdu eğer parametre olsaydı). SayacWidget'ın tek görevi, createState() metoduyla kendi state'ini oluşturan başka bir nesneyi işaret etmektir.

2. _SayacWidgetState (State'ten türeyen):

dart
class _SayacWidgetState extends State<SayacWidget> {
  int _sayac = 0; // BU alan DEĞİŞEBİLİR — final değil!
  // ...
}

İşte gerçek "değişebilir" veri burada yaşıyor — _sayac, final değil, var gibi normal bir alan (Dart Bölüm 2'yi hatırla). Bu class, Dart Bölüm 9'da öğrendiğimiz generic sınıflara bir örnek: State<SayacWidget>, "bu state, SayacWidget tipindeki bir widget'a ait" anlamına gelir.

Neden alt çizgi ile başlıyor (_SayacWidgetState)? Dart Bölüm 6 Konu 30'u hatırlarsan — _ öneki, bu class'ı dosyaya özel (private) yapar. Bu, Flutter'ın bir konvansiyonudur: State class'ları genelde private tutulur, çünkü dışarıdan doğrudan erişilmesine gerek yoktur.

setState() — Rebuild Mekanizmasının Kalbi

dart
void _arttir() {
  setState(() {
    _sayac++;
  });
}

setState(), Flutter'a "state'im değişti, lütfen build()'i tekrar çağır" demenin yoludur. Bu, kritik bir kural: _sayac++; yazarak değeri değiştirmek tek başına yeterli değildir — eğer bunu setState() içine koymazsan, Dart'ın belleğinde değer değişir ama Flutter, ekranı güncellemesi gerektiğini bilmez!

dart
void _arttirYanlisYontem() {
  _sayac++; // ❌ değer değişti AMA Flutter bunu bilmiyor, ekran GÜNCELLENMEZ
}

void _arttirDogruYontem() {
  setState(() { // ✅ Flutter'a "değişiklik oldu, build() çağır" diyoruz
    _sayac++;
  });
}

Bu, Dart Bölüm 5'te öğrendiğimiz closure kavramının doğrudan bir uygulamasıdır — setState(), parametre olarak bir anonim fonksiyon (Dart Bölüm 5 Konu 24-25'i hatırla) alıyor, bu fonksiyonu çalıştırıyor (içindeki değişiklikleri uyguluyor), sonra Flutter'a "artık build()'i tekrar çağırabilirsin" sinyalini veriyor.

Rebuild Mekanizması — Adım Adım Ne Oluyor?

setState() çağrıldığında, arka planda şu adımlar gerçekleşiyor (Bölüm 1 Konu 1'de öğrendiğimiz üç katmanlı mimariyi hatırla):

  1. setState() içindeki kod çalışır — _sayac değeri gerçekten değişir (bu, sıradan bir Dart değişken ataması).
  2. Flutter, bu widget'ın (ve State'inin) "kirli" (dirty) olduğunu işaretler — yani "yeniden çizilmesi gerekiyor" der.
  3. Flutter'ın zamanlayıcısı (scheduler), bir sonraki "frame" (ekran yenileme döngüsü) sırasında, bu widget'ın build() metodunu tekrar çağırır.
  4. build(), yeni bir widget ağacı döndürür (yeni _sayac değeriyle).
  5. Bölüm 1 Konu 1'de öğrendiğimiz gibi, Flutter, eski ve yeni widget ağacını karşılaştırır (diffing) ve sadece gerçekten değişen RenderObject'leri günceller — tüm ekranı sıfırdan çizmez.

Bu son adım kritik: setState(), "her şeyi sıfırdan çiz" demek değildir — Flutter, akıllıca, sadece neyin değiştiğini bulup minimum işi yapar. Bu, Dart Bölüm 13 Konu 62'de öğrendiğimiz performans bilinçli tasarım felsefesinin somut bir örneği.

setState()'in Kapsamı — Sadece O Widget mı, Tüm Ağaç mı?

dart
class _SayacWidgetState extends State<SayacWidget> {
  int _sayac = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Sayaç: $_sayac'), // bu ve alt widget'lar YENİDEN OLUŞTURULUR
        BuyukResimWidget(), // bu da rebuild olur, EĞER const değilse!
      ],
    );
  }
}

Önemli detay: setState() çağrıldığında, o State'in build() metodu tamamen yeniden çalışır — bu, build() içinde tanımlanan tüm widget ağacının (o State'e ait kısmın) yeniden oluşturulacağı anlamına gelir. Eğer BuyukResimWidget() gibi, değişmeyen bir widget varsa, bunu const yaparak (Bölüm 2 Konu 5'i ve Dart Bölüm 13 Konu 62'yi hatırla), Flutter'ın bu widget'ı gereksiz yere yeniden oluşturmaktan kaçınmasını sağlayabilirsin — bunu Bölüm 9'da (performans optimizasyonu) tam olarak işleyeceğiz.

Ne Zaman StatelessWidget, Ne Zaman StatefulWidget?

StatelessWidget kullan, eğer:

  • Widget, oluşturulduktan sonra hiç değişmeyecekse (sadece dışarıdan aldığı parametrelere göre görünür).
  • Widget'ın kendi iç durumu (internal state) yoksa.

StatefulWidget kullan, eğer:

  • Widget'ın, kullanıcı etkileşimi, zamanlayıcı, ağ cevabı gibi bir şeyle zamanla değişen bir verisi varsa.
  • Örnekler: bir form alanının içeriği, bir animasyonun ilerleme durumu, bir API çağrısının sonucu (Bölüm 5'te göreceğiz).

Pratik bir kural: Eğer bir widget içinde _degisken = yeniDeger; yazman gerekiyorsa ve bu değişikliğin ekranda görünmesini istiyorsan, bu bir StatefulWidget olmalı — ve o değişikliği her zaman setState() içine sarmalısın.

initState() — İlk Bakış (Detaylı Olarak Bölüm 4/5'te)

dart
class _SayacWidgetState extends State<SayacWidget> {
  int _sayac = 0;

  @override
  void initState() {
    super.initState(); // Dart Bölüm 7'de öğrendiğimiz super çağrısı — ÖNCE üst sınıfın davranışı çalışmalı
    print('Widget ilk kez oluşturuldu');
  }

  // ...
}

initState(), bir State nesnesi ilk kez oluşturulduğunda, bir kere çağrılan bir metoddur — Dart Bölüm 6'da öğrendiğimiz constructor mantığına benzer, ama Flutter'ın kendi yaşam döngüsüne özgü bir kavram. Bunu, Bölüm 5'te (asenkron veri çekme) ve Bölüm 8'de (state management) çok daha derinlemesine işleyeceğiz.


🎯 Bu Dersten Çıkarılması Gerekenler

  • StatelessWidget, oluşturulduktan sonra değişmeyen widget'lardır; StatefulWidget, zamanla değişebilen bir "state" nesnesine sahiptir.
  • StatefulWidget, iki class'tan oluşur: widget'ın kendisi (immutable) ve State sınıfı (değişebilir veri burada tutulur).
  • setState(), Flutter'a "state değişti, build()'i tekrar çağır" demenin tek doğru yoludur — setState() olmadan yapılan değişiklikler, veriyi değiştirir ama ekrana yansımaz.
  • setState() çağrıldığında, Flutter build()'i tekrar çalıştırır, ama Widget/Element/RenderObject ayrımı sayesinde sadece gerçekten değişen kısımları günceller.
  • State class'ları genelde _ ile private tutulur (Flutter konvansiyonu).

📝 Ödevler

  • [ ] Kendi basit bir sayaç StatefulWidget'ı yaz (artı/eksi butonlu), setState() ile doğru şekilde güncelle.
  • [ ] Bilerek setState() kullanmadan bir değişkeni değiştir, ekranın güncellenmediğini gözlemle; sonra setState() ekleyip düzelt.
  • [ ] initState() içine bir print() ekle, widget ilk oluştuğunda bunun bir kere çalıştığını, ama setState() sonrası tekrar çalışmadığını gözlemle.
  • [ ] Bir StatefulWidget içinde, hem değişen (state'e bağlı) hem değişmeyen (const yapılabilecek) widget'lar bulundur — hangisinin hangisi olduğunu belirle.
  • [ ] Kendi cümlelerinle, "neden StatefulWidget iki ayrı sınıfa bölünmüş, tek sınıfta neden olmuyor" sorusunu, immutability (değişmezlik) kavramıyla ilişkilendirerek açıkla.

Sıradaki konu: Bölüm 2 — Konu 7: Temel Layout: Container, Row, Column, Stack, Padding, Align, Center