Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
Dizi · 13/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
- "Composition Over Inheritance" Ne Demek?
- Ne Zaman Yeni Bir Widget Yazmalısın?
- Widget Parçalama — "Görsel Karmaşıklığı Yönetme"
- Fonksiyon mu, Widget Class'ı mı? — Yaygın Bir Tuzak
- Parametrelerle Esnek Widget'lar
- 🎯 Bu Dersten Çıkarılması Gerekenler
- 📝 Ödevler
- 🎮 Mini Uygulama — Yeniden Kullanılabilir Bileşen Kütüphanesi
Şimdiye kadar birkaç kendi widget'ımızı yazdık (AnaEkran, ProfilKarti gibi) ama bunun arkasındaki prensipleri sistematik olarak işlemedik. Bu ders, iyi bir Flutter geliştiricisinin nasıl düşündüğünü öğretiyor.
"Composition Over Inheritance" Ne Demek?
Bu, yazılım mühendisliğinde genel bir prensiptir: "kalıtım yerine kompozisyonu tercih et." Dart Bölüm 7'de hem extends (kalıtım) hem de widget'ları iç içe geçirmeyi (kompozisyon) öğrendik — Flutter, neredeyse her zaman kompozisyonu tercih eder.
Kalıtım ile yaklaşım (Flutter'da NADİREN kullanılır):
// ❌ Genelde ÖNERİLMEZ — mevcut bir widget'tan extends etmeye çalışmak
class BuyukKirmiziMetin extends Text {
BuyukKirmiziMetin(String data) : super(data, style: TextStyle(fontSize: 24, color: Colors.red));
}Kompozisyon ile yaklaşım (Flutter'ın standart yolu):
// ✅ Doğru yaklaşım — yeni bir widget yaz, içinde Text'i KULLAN
class BuyukKirmiziMetin extends StatelessWidget {
final String data;
const BuyukKirmiziMetin(this.data, {super.key});
@override
Widget build(BuildContext context) {
return Text(
data,
style: TextStyle(fontSize: 24, color: Colors.red),
);
}
}Neden Flutter kompozisyonu tercih ediyor? Bölüm 1 Konu 1'de öğrendiğimiz Widget/Element/RenderObject üçlü mimarisini hatırlarsan — Flutter'ın çoğu widget'ı, karmaşık iç davranışlara sahiptir. Bunlardan extends etmeye çalışmak, o widget'ın iç detaylarına bağımlı olmak demektir (Dart Bölüm 7'de "kırılgan taban sınıf" (fragile base class) sorunundan bahsetmiştik dolaylı olarak) — Flutter ekibi, widget'ların iç yapısını değiştirebilme özgürlüğünü korumak için, sen widget'ları extend etmek yerine, onları build() içinde kullanarak yeni widget'lar oluşturasın diye tasarlamıştır.
Ne Zaman Yeni Bir Widget Yazmalısın?
Kural: Aynı widget ağacı parçasını iki veya daha fazla yerde kopyalıyorsan, bunu bir widget'a çıkar.
// ❌ Kod tekrarı — aynı yapı iki yerde
Widget build(BuildContext context) {
return Column(
children: [
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
child: Text('Kart 1'),
),
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
child: Text('Kart 2'),
),
],
);
}// ✅ Doğrusu — tekrarlanan yapıyı bir widget'a çıkar
class KartWidget extends StatelessWidget {
final String metin;
const KartWidget({super.key, required this.metin});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
child: Text(metin),
);
}
}
// Kullanımı:
Column(
children: [
KartWidget(metin: 'Kart 1'),
KartWidget(metin: 'Kart 2'),
],
)Bu, Dart Bölüm 5'te öğrendiğimiz "kod tekrarını fonksiyonlarla önleme" prensibinin, Flutter'daki widget'lara uygulanmış hali — KartWidget, aslında bir Dart class'ı olduğu için, bu tam olarak Dart Bölüm 6'da öğrendiğimiz "yeniden kullanılabilir yapı taşları" felsefesidir.
Widget Parçalama — "Görsel Karmaşıklığı Yönetme"
// ❌ Her şey tek bir DEVASA build() metodunda
class AnaEkran extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Ana Sayfa')),
body: Column(
children: [
// 50 satır profil kartı kodu...
// 40 satır istatistik grafiği kodu...
// 60 satır liste kodu...
],
),
);
}
}// ✅ Küçük widget'lara bölünmüş
class AnaEkran extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Ana Sayfa')),
body: Column(
children: [
ProfilKarti(),
IstatistikGrafigi(),
UrunListesi(),
],
),
);
}
}Bu ayrım, sadece kod tekrarını önlemekle kalmaz — Bölüm 2 Konu 6'da öğrendiğimiz setState()/rebuild mekanizmasıyla da doğrudan ilişkilidir: eğer ProfilKarti içinde bir setState() çağrılırsa, sadece ProfilKarti'nin kendi build()'i tekrar çalışır — IstatistikGrafigi ve UrunListesi etkilenmez. Eğer her şey tek bir dev widget içinde olsaydı, herhangi bir küçük değişiklik, tüm ekranın yeniden oluşturulmasını (rebuild) tetiklerdi — bu, Dart Bölüm 13'te öğrendiğimiz performans bilinçli tasarım felsefesine aykırı olurdu.
Fonksiyon mu, Widget Class'ı mı? — Yaygın Bir Tuzak
// ❌ ÖNERİLMEZ — bir metod widget "gibi" görünse de, gerçek bir widget DEĞİLDİR
Widget kartOlustur(String metin) {
return Container(
padding: EdgeInsets.all(16),
child: Text(metin),
);
}// ✅ DOĞRUSU — gerçek bir widget class'ı
class KartWidget extends StatelessWidget {
final String metin;
const KartWidget({super.key, required this.metin});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Text(metin),
);
}
}Bu ayrım, başlangıçta görsel olarak çok benzer sonuçlar üretir, ama Bölüm 1 Konu 1'de öğrendiğimiz Element ağacı açısından kritik bir fark vardır: bir fonksiyon (kartOlustur), her çağrıldığında sadece bir Dart fonksiyon çağrısıdır — Flutter'ın Element ağacında kendi kimliğine sahip bir "node" oluşturmaz. Gerçek bir widget class'ı (KartWidget) ise, Element ağacında kendi kimliğine sahip bir varlıktır — bu, Flutter'ın daha akıllı rebuild optimizasyonları yapabilmesini sağlar (Bölüm 9'da performans konusunda bunu tam olarak işleyeceğiz). Ayrıca, gerçek bir widget class'ı, Flutter DevTools'ta (Bölüm 1 Konu 2'yi hatırla) kendi widget'ı olarak görünür ve incelenebilir — bir fonksiyon bu görünürlüğü sağlamaz.
Pratik kural: Widget döndüren yeniden kullanılabilir bir parça yazacaksan, her zaman bir class (StatelessWidget/StatefulWidget) kullan, fonksiyon değil.
Parametrelerle Esnek Widget'lar
class Rozet extends StatelessWidget {
final String metin;
final Color renk;
final VoidCallback? onTap; // Dart Bölüm 5'te öğrendiğimiz fonksiyon TİPİ!
const Rozet({
super.key,
required this.metin,
this.renk = Colors.blue, // Dart Bölüm 5'te öğrendiğimiz varsayılan değer
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: renk,
borderRadius: BorderRadius.circular(16),
),
child: Text(metin, style: TextStyle(color: Colors.white)),
),
);
}
}VoidCallback? — Bu, Dart Bölüm 5 Konu 24'te öğrendiğimiz fonksiyon tipini doğrudan kullanıyor: VoidCallback, aslında Flutter'ın (Dart'ın) tanımladığı bir typedef'tir — void Function() için bir kısayoldur. ? ile nullable yapılmış (Dart Bölüm 2'yi hatırla) — yani onTap verilmeyebilir, bu durumda tıklama hiçbir şey yapmaz.
Bu widget, artık her yerde farklı renk/metin/davranışla yeniden kullanılabilir:
Rozet(metin: 'Yeni', renk: Colors.green, onTap: () => print('Tıklandı'))
Rozet(metin: 'İndirimli') // renk varsayılan mavi, onTap yok🎯 Bu Dersten Çıkarılması Gerekenler
- Flutter, "composition over inheritance" prensibini benimser — var olan widget'lardan
extendsetmek yerine, onlarıbuild()içinde kullanarak yeni widget'lar oluşturursun. - Tekrarlanan widget ağacı parçalarını, ayrı bir widget class'ına çıkarmak, hem kod tekrarını önler hem de rebuild verimliliğini artırır.
- Widget döndüren yardımcı bir parça yazarken, fonksiyon değil, gerçek bir widget class'ı kullanmalısın — Element ağacı kimliği ve rebuild optimizasyonu açısından fark yaratır.
- Parametreli, esnek widget'lar (varsayılan değerler, nullable callback'ler ile), Dart Bölüm 5'te öğrendiğin fonksiyon parametreleri bilgisini doğrudan kullanır.
📝 Ödevler
- [ ] Kod tekrarı içeren bir widget ağacını (aynı yapıyı iki kere yazarak), sonra bunu ayrı bir widget class'ına çıkararak iki versiyonunu yaz.
- [ ] Bir widget döndüren fonksiyon yaz, sonra bunu gerçek bir
StatelessWidgetclass'ına dönüştür — aradaki farkı kendi cümlelerinle açıkla. - [ ] En az 3 parametreli (biri varsayılan değerli, biri nullable callback) kendi custom widget'ını yaz, farklı konfigürasyonlarla en az 2 kere kullan.
- [ ] Bir ekranı, "tek dev widget" yerine en az 3 ayrı widget class'ına bölerek yeniden düzenle.
- [ ] Kendi cümlelerinle, "Flutter'da neden var olan widget'lardan extends etmek yaygın değil, ama kendi StatelessWidget'larımızı yazmak yaygın" sorusunu açıkla.
🎮 Mini Uygulama — Yeniden Kullanılabilir Bileşen Kütüphanesi
Bölüm 3'te öğrendiklerini birleştiren bir mini proje: kendi küçük widget kütüphaneni oluştur.
Gereksinimler:
- En az 3 farklı, yeniden kullanılabilir custom widget yaz (örneğin:
Rozet,IstatistikKutusu,KullaniciSatiri). - Her widget, en az 2 parametre alsın (biri varsayılan değerli veya nullable olsun).
- Bu widget'ları,
ListView.builder(Bölüm 3 Konu 11'i hatırla) ile oluşturulan bir listede, farklı verilerle en az 5 kere kullan. - Tüm ekran,
Scaffold+AppBarile sarmalanmış olsun (Bölüm 3 Konu 10'u hatırla).
Bu proje, artık senin kendi widget'larını tasarlama becerini gerçek bir bağlamda test ediyor — Bölüm 2 ve Bölüm 3'ün tamamını (layout, ekran iskeleti, listeler, custom widget'lar) bir araya getiriyor.
Sıradaki konu: Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes