↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)

4 dk okuma #flutter
Dizi · 47/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 8 başlık
  1. Rebuild Nedir, Neden Pahalıdır?
  2. const Widget'lar — En Ucuz Optimizasyon
  3. key — Widget Kimliğini Koruma
  4. Key Türleri — Ne Zaman Hangisi?
  5. Widget'ları Küçük Parçalara Bölmek — Rebuild Kapsamını Daraltma
  6. Rebuild'i Gözle Görmek — DevTools'a Bir Önizleme
  7. 🎯 Bu Dersten Çıkarılması Gerekenler
  8. 📝 Ödevler

Konu 45-46'da kodun doğruluğunu garanti altına almayı öğrendik. Şimdi kodun hızına odaklanıyoruz. Bu derste, Flutter uygulamalarındaki en yaygın performans sorununun kaynağına — gereksiz rebuild'lere — ve bunları önlemenin iki temel aracına (const, key) bakacağız.

Rebuild Nedir, Neden Pahalıdır?

Bölüm 2 Konu 6'da öğrendiğimiz setState()'i hatırla: bir state değiştiğinde, Flutter, ilgili widget'ın (ve varsayılan olarak onun tüm alt widget ağacının) build() metodunu yeniden çalıştırır. build() metodu genelde hızlıdır (sadece yeni bir widget ağacı tanımlar, hemen çizmez) — ama binlerce widget'ı olan büyük bir ekranda, saniyede 60 kez gereksiz yere tüm ağacı yeniden build() etmek, gözle görülür bir yavaşlamaya (kasma, "jank") yol açabilir.

Kritik ayrım: build() çağrılması ≠ ekranın yeniden çizilmesi. Flutter, build()'den dönen widget ağacını, bir önceki ağaçla karşılaştırır (Bölüm 1'de değindiğimiz "diffing" mekanizması) ve sadece gerçekten değişen kısımları native katmana (Skia/Impeller) gönderir. Ama bu karşılaştırma işleminin kendisi de ücretsiz değildir — gereksiz build() çağrıları, gereksiz karşılaştırma işi demektir.

const Widget'lar — En Ucuz Optimizasyon

dart
// ❌ Her rebuild'de YENİDEN OLUŞTURULUR
class BaslikMetni extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Sabit Başlık', style: TextStyle(fontSize: 20));
  }
}

// ✅ const ile — Flutter bunu "hiç değişmeyecek" olarak işaretler
class BaslikMetni extends StatelessWidget {
  const BaslikMetni({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text('Sabit Başlık', style: TextStyle(fontSize: 20));
  }
}

Dart Bölüm 1'de öğrendiğimiz const anahtar kelimesini hatırla — derleme zamanında sabit bir değer üretir. Bir widget const olarak oluşturulduğunda, Flutter şunu garanti eder: bu widget, ebeveyni yeniden çizildiğinde bile, birebir aynı nesne örneğidir (bellekte tek bir kopya, her build() çağrısında yeniden oluşturulmaz). Flutter, ebeveyn widget'ı yeniden build() ederken, const olarak işaretlenmiş çocukların build()'ini bile çağırmadan atlayabilir — çünkü "bunun değişmeyeceğini zaten biliyor."

dart
class Ekran extends StatefulWidget {
  const Ekran({super.key});
  @override
  State<Ekran> createState() => _EkranState();
}

class _EkranState extends State<Ekran> {
  int _sayac = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const PahaliBaslikWidget(), // setState çağrılsa bile YENİDEN OLUŞTURULMAZ
        Text('Sayaç: $_sayac'),      // bu her seferinde yeniden oluşur (normal, gerekli)
        ElevatedButton(
          onPressed: () => setState(() => _sayac++),
          child: const Text('Artır'),
        ),
      ],
    );
  }
}

Neden PahaliBaslikWidget gerçekten const olabiliyor? Çünkü _sayac'a hiç bağımlı değil — hangi state değişirse değişsin, her zaman aynı görünecek. Bu, const'un altın kuralı: bir widget'ın görünümü, hiçbir dış state'e bağlı değilse, const yapılabilir.

Pratik tavsiye: flutter analyze (Konu 46'da gördüğümüz statik analiz aracı), const yapılabilecek ama yapılmamış widget'ları genelde otomatik olarak uyarır (prefer_const_constructors linter kuralı) — bu, elle her yeri kontrol etmene gerek bırakmayan ücretsiz bir optimizasyon önerisi kaynağıdır.

key — Widget Kimliğini Koruma

Bölüm 8 Konu 35'te AnimatedSwitcher için Key kavramına kısaca değinmiştik — şimdi neden genel olarak önemli olduğunu tam anlayacağız.

dart
// ❌ SORUNLU — key olmadan liste yeniden sıralanırsa
class GorevListesi extends StatelessWidget {
  final List<Gorev> gorevler;
  const GorevListesi({super.key, required this.gorevler});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: gorevler.map((g) => GorevSatiri(gorev: g)).toList(), // key YOK
    );
  }
}

Sorun ne zaman ortaya çıkar? gorevler listesindeki sıralama değiştiğinde (örn. bir görev en üste taşındığında) ya da listenin ortasından bir eleman silindiğinde. Flutter, widget'ları eşleştirirken varsayılan olarak tip ve konuma (index) bakar — key olmadan, Flutter "3. sıradaki widget, hâlâ 3. sıradaki widget" diye düşünür, içeriği farklı olsa bile. Bu, özellikle her satırın kendi lokal state'i (örn. bir TextField'ın yazılmış metni, bir Checkbox'ın açık/kapalı durumu) varsa, yanlış widget'ın yanlış state'i taşımasına yol açar — kullanıcı 2. satırı işaretler, liste yeniden sıralanır, işaretli görünen artık başka bir görev olur!

dart
// ✅ DOĞRU — her widget'a BENZERSİZ bir kimlik
Column(
  children: gorevler.map((g) => GorevSatiri(key: ValueKey(g.id), gorev: g)).toList(),
)

ValueKey(g.id) — Bölüm 8 Konu 37'de Hero'nun tag'i için öğrendiğimiz "benzersiz, dinamik kimlik" mantığının birebir aynısı. key: ValueKey(g.id) verdiğinde, Flutter artık "3. sıradaki widget" değil, "ID'si 42 olan görev widget'ı" diye takip eder — sıralama değişse bile, doğru state, doğru veriyle eşleşmeye devam eder.

Key Türleri — Ne Zaman Hangisi?

dart
ValueKey(g.id)      // bir DEĞERE göre eşitlik (en sık kullanılan)
ObjectKey(kullanici) // bir NESNENİN kendisine göre eşitlik (== operatörü ile)
UniqueKey()          // HER ZAMAN benzersiz — widget'ı "kesinlikle farklı" yapmak için
GlobalKey()          // widget ağacında HERHANGİ bir yerden erişim (Bölüm 4 Konu 15'te GlobalKey<FormState>'i hatırla!)

ValueKey, Dart Bölüm 3'te öğrendiğimiz eşitlik (==) kavramına dayanır — iki ValueKey, taşıdıkları değer eşitse aynı kabul edilir. Bu, listelerde en sık ihtiyaç duyduğun türdür.

GlobalKey'i aslında daha önce kullanmıştık — Bölüm 4 Konu 15'te form validasyonu için GlobalKey<FormState>. Şimdi bunun neden GlobalKey olduğunu tam anlıyorsun: normal bir widget'ın state'ine sadece kendi alt ağacından erişebilirsin, ama GlobalKey, widget ağacının herhangi bir yerinden, o widget'ın State nesnesine doğrudan erişim sağlar — bu yüzden "global."

Uyarı — GlobalKey'i fazla kullanma: GlobalKey, widget ağacının normal, tek yönlü veri akışını (yukarıdan aşağıya) bypass eder — bu, Bölüm 6'da öğrendiğimiz state management prensiplerine aykırı bir "arka kapı"dır. Sadece gerçekten gerektiğinde (form durumuna erişim, bir widget'ın boyutunu ölçme gibi) kullanılmalı, genel state paylaşımı için değil (onun için Provider/Riverpod var).

Widget'ları Küçük Parçalara Bölmek — Rebuild Kapsamını Daraltma

dart
// ❌ Kötü — tüm ekran, sadece sayaç değiştiğinde yeniden çiziliyor
class Ekran extends StatefulWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        CokKarmasikBirGrafik(veri: sabitVeri), // GEREKSİZ yeniden çiziliyor!
        Text('Sayaç: $_sayac'),
      ],
    );
  }
}

// ✅ İyi — grafiği AYRI bir widget'a çıkar, const yap
class Ekran extends StatefulWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const CokKarmasikBirGrafik(veri: sabitVeri), // artık const, atlanıyor
        Text('Sayaç: $_sayac'),
      ],
    );
  }
}

Bu, Bölüm 3 Konu 13'te öğrendiğimiz custom widget yazma / kompozisyon prensibinin performans yönü: her StatefulWidget'ın build() metodu ne kadar küçük ve odaklı olursa, gereksiz yere yeniden çizilen kod o kadar az olur. Bölüm 6 Konu 24'te Provider'ın context.select()'i, Bölüm 6 Konu 25'te Riverpod'da ref.watch(provider.select(...)) de aynı hedefe hizmet ediyordu — sadece gerçekten değişen kısmı yeniden çizme.

Rebuild'i Gözle Görmek — DevTools'a Bir Önizleme

Konu 48'de Flutter DevTools'un "Widget Rebuild" sekmesini detaylı göreceğiz — bu araç, ekranındaki hangi widget'ların ne sıklıkla yeniden build() edildiğini görsel olarak gösterir. Şimdilik bilmen gereken: bu derste öğrendiğin const/key optimizasyonlarının gerçekten işe yarayıp yaramadığını, tahmin etmek yerine bu araçla ölçerek doğrulayabileceksin.


🎯 Bu Dersten Çıkarılması Gerekenler

  • build() çağrılması, ekranın fiilen yeniden çizilmesi değildir — ama gereksiz build() çağrıları hâlâ maliyetlidir (diffing işi).
  • const widget'lar, hiçbir dış state'e bağlı olmayan widget'lar için kullanılır; Flutter, const işaretli widget'ların build()'ini rebuild sırasında tamamen atlayabilir.
  • key (özellikle ValueKey), listelerde sıralama değişen/silinen elemanların doğru state ile eşleşmesini garanti eder — eksikliği, yanlış widget'ın yanlış veriyle eşleşmesine yol açabilir.
  • GlobalKey, widget ağacının herhangi bir yerinden bir State'e erişim sağlar (örn. GlobalKey<FormState>) ama genel state paylaşımı için değil, spesifik/nadir durumlar için kullanılmalıdır.
  • Büyük widget'ları küçük, odaklı parçalara bölmek, gereksiz rebuild'lerin kapsamını daraltır — bu, Provider/Riverpod'daki select() ile aynı hedefi paylaşır.

📝 Ödevler

  • [ ] Bir ekranda, flutter analyze'ın const önerdiği en az 3 yeri bul ve düzelt.
  • [ ] Key olmadan bir görev listesi yap, listeyi (örn. bir "en üste taşı" butonuyla) yeniden sırala, her satırda bir Checkbox varsa state'in karıştığını gözlemle; sonra ValueKey ekleyip sorunun düzeldiğini doğrula.
  • [ ] Karmaşık (örn. çok sayıda widget içeren) bir alt widget'ı const yapılabilir hale getirip, geri kalan ekranın state'i değiştiğinde bu widget'ın yeniden oluşturulmadığını (DevTools olmadan, örneğin build() içine bir print koyarak) doğrula.
  • [ ] Kendi cümlelerinle, "key olmadan bir liste yeniden sıralandığında neden yanlış widget'ın yanlış state'i taşıyabileceğini" bir örnekle açıkla.

Sıradaki konu: Bölüm 10 — Konu 48: DevTools Profiling