Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
Dizi · 47/61 Flutter Türkçe Tutorial
- Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
- Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
- Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
- Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
- Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
- Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
- Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
- Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
- Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
- Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
- Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
- Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
- Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
- Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
- Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
- Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
- Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
- Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
- Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
- Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
- Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
- Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
- Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
- Bölüm 6 — Konu 24: Provider Paketi
- Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
- Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
- Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
- Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
- Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
- Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
- Bölüm 7 — Konu 31: Deep Linking
- Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
- Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
- Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
- Bölüm 8 — Konu 35: Implicit Animasyonlar
- Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
- Bölüm 8 — Konu 37: Hero Animasyonları
- Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
- Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
- Bölüm 9 — Konu 40: Platform Channels
- Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
- Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
- Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
- Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
- Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
- Bölüm 10 — Konu 46: CI/CD
- Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
- Bölüm 10 — Konu 48: DevTools Profiling
- Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
- Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
- Bölüm 11 — Konu 51: Supabase Alternatifi
- Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
- Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
- Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
- Bölüm 12 — Konu 55: Flutter Web / Desktop
- Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
- Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
- Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
- Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
- Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
- Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
İçindekiler 8 başlık
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
// ❌ 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."
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.
// ❌ 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!
// ✅ 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?
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
// ❌ 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 gereksizbuild()çağrıları hâlâ maliyetlidir (diffing işi).constwidget'lar, hiçbir dış state'e bağlı olmayan widget'lar için kullanılır; Flutter,constişaretli widget'larınbuild()'ini rebuild sırasında tamamen atlayabilir.key(özellikleValueKey), 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'ınconstö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
Checkboxvarsa state'in karıştığını gözlemle; sonraValueKeyekleyip sorunun düzeldiğini doğrula. - [ ] Karmaşık (örn. çok sayıda widget içeren) bir alt widget'ı
constyapılabilir hale getirip, geri kalan ekranın state'i değiştiğinde bu widget'ın yeniden oluşturulmadığını (DevTools olmadan, örneğinbuild()içine birprintkoyarak) doğrula. - [ ] Kendi cümlelerinle, "
keyolmadan 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