Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
Dizi · 15/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
Bölüm 4'e hoş geldin. Bu derste, kullanıcıdan veri almayı (form doldurma) öğreneceğiz — bu, Bölüm 2'de öğrendiğimiz StatefulWidget/setState() bilgisinin gerçek bir kullanım alanı.
TextField — En Temel Metin Girişi
TextField(
decoration: InputDecoration(
labelText: 'İsim',
border: OutlineInputBorder(),
),
)TextField, kullanıcının serbest metin girebildiği bir widget'tır. Ama dikkat et — bu widget, girdiğin metni kendi başına saklamaz, sadece gösterir. Girilen metne erişmek için bir controller gerekir.
TextEditingController — Girdiye Programatik Erişim
class GirisFormu extends StatefulWidget {
const GirisFormu({super.key});
@override
State<GirisFormu> createState() => _GirisFormuState();
}
class _GirisFormuState extends State<GirisFormu> {
final TextEditingController _isimController = TextEditingController();
@override
void dispose() {
_isimController.dispose(); // Bölüm 4 Konu 17'de dispose'u tam işleyeceğiz
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(controller: _isimController),
ElevatedButton(
onPressed: () {
print('Girilen isim: ${_isimController.text}');
},
child: Text('Yazdır'),
),
],
);
}
}_isimController.text, Dart Bölüm 2'de öğrendiğimiz String tipinde, kullanıcının o an girdiği metni verir. TextEditingController, Dart Bölüm 11 Konu 52'de öğrendiğimiz cascade notasyonuna benzer bir kalıpla da kullanılabilir (TextEditingController()..text = 'Başlangıç değeri').
dispose() neden çağrılıyor? Bölüm 4 Konu 17'de bunu tam işleyeceğiz, ama şimdiden bilmen faydalı: TextEditingController, Dart Bölüm 13 Konu 62'de öğrendiğimiz garbage collection'ın otomatik olarak temizleyemediği bazı kaynakları (native platform kaynakları) tutar — bu yüzden elle serbest bırakılması gerekir, tıpkı Dart Bölüm 13 Konu 64'te (FFI) öğrendiğimiz "elle bellek yönetimi" istisnasına benzer bir durum.
Form ve TextFormField — Yapılandırılmış Form Yönetimi
Tek tek TextField'ları yönetmek yerine, Flutter, birden fazla alanı gruplandırıp birlikte doğrulamanı sağlayan Form widget'ını sunar:
class KayitFormu extends StatefulWidget {
const KayitFormu({super.key});
@override
State<KayitFormu> createState() => _KayitFormuState();
}
class _KayitFormuState extends State<KayitFormu> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'E-posta'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'E-posta boş olamaz';
}
if (!value.contains('@')) {
return 'Geçerli bir e-posta girin';
}
return null; // null = geçerli, hata yok
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print('Form geçerli!');
}
},
child: Text('Gönder'),
),
],
),
);
}
}GlobalKey<FormState> — Ne İşe Yarıyor?
Bu, ilk bakışta kafa karıştırıcı görünebilir — parça parça açalım.
GlobalKey, bir widget'a "dışarıdan, doğrudan erişim" sağlayan özel bir anahtardır. Normalde, Flutter'da bir widget'ın State'ine (Bölüm 2 Konu 6'da öğrendiğimiz State sınıfını hatırla), sadece o widget'ın kendisi (kendi setState()'i aracılığıyla) erişebilir. GlobalKey, bu kuralın bilinçli bir istisnasıdır — widget ağacının dışından, o widget'ın State'ine doğrudan referans almanı sağlar.
final _formKey = GlobalKey<FormState>(); // GENERİC! Dart Bölüm 9'u hatırlaGlobalKey<FormState> — Dart Bölüm 9'da öğrendiğimiz generic sınıflar burada karşımıza çıkıyor! GlobalKey<T>, "T tipinde bir State'e erişim sağlayan anahtar" demek — burada T, FormState (Form widget'ının kendi State sınıfı).
Form(
key: _formKey, // bu anahtarı Form widget'ına "veriyoruz"
child: ...,
)_formKey.currentState!.validate()currentState, _formKey'in bağlı olduğu widget'ın o anki State nesnesini döndürür — bu, FormState? tipindedir (nullable, çünkü widget henüz oluşturulmamış olabilir). ! (Dart Bölüm 2'de öğrendiğimiz null assertion operatörü) ile "bu kesinlikle null değil" diyoruz — çünkü Form widget'ı zaten build() içinde oluşturulduğu için, bu noktada State'in var olduğundan eminiz.
.validate(), Form içindeki tüm TextFormField'ların validator fonksiyonlarını sırayla çalıştırır ve hepsi null döndürürse (yani hata yoksa) true döner.
validator Fonksiyonu — Dart Bilginle Çözümleme
validator: (value) {
if (value == null || value.isEmpty) {
return 'E-posta boş olamaz';
}
return null;
}Bu, Dart Bölüm 5'te öğrendiğimiz anonim fonksiyondur — String? parametre alır (kullanıcının girdiği metin, henüz hiçbir şey girilmemişse null olabilir), String? döndürür:
nulldöndürürse → "hata yok, geçerli."- Bir
Stringdöndürürse → bu string, hata mesajı olarak kullanıcıya gösterilir.
Bu, Dart Bölüm 2'de öğrendiğimiz null safety'nin, "var/yok" (hata var/yok) durumunu ifade etmek için yaratıcı bir kullanımı.
Birden Fazla Alan ile Tam Bir Form
class KayitFormu extends StatefulWidget {
const KayitFormu({super.key});
@override
State<KayitFormu> createState() => _KayitFormuState();
}
class _KayitFormuState extends State<KayitFormu> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _sifreController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_sifreController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'E-posta'),
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Geçerli bir e-posta girin';
}
return null;
},
),
TextFormField(
controller: _sifreController,
obscureText: true, // şifre gizleme
decoration: InputDecoration(labelText: 'Şifre'),
validator: (value) {
if (value == null || value.length < 6) {
return 'Şifre en az 6 karakter olmalı';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print('E-posta: ${_emailController.text}');
print('Şifre: ${_sifreController.text}');
}
},
child: Text('Kayıt Ol'),
),
],
),
);
}
}🎯 Bu Dersten Çıkarılması Gerekenler
TextField, serbest metin girişi sağlar ama girdiyi kendi başına saklamaz;TextEditingController, girdiye programatik erişim sağlar.Form+TextFormField, birden fazla alanı gruplandırıp birlikte doğrulamayı (validate) sağlar.GlobalKey<FormState>, widget ağacının dışından bir widget'ın State'ine doğrudan erişim sağlayan özel bir anahtardır (Dart'taki generics'in gerçek bir kullanımı).validatorfonksiyonu,null(geçerli) veya birString(hata mesajı) döndürür — Dart'ın null safety'sinin yaratıcı bir kullanımı.- Controller'lar, Dart'ın garbage collector'ının otomatik temizleyemediği kaynaklar tuttuğu için
dispose()ile elle serbest bırakılmalıdır.
📝 Ödevler
- [ ]
TextEditingControllerile birTextField'ın girdisini bir butona basıncaprintile yazdır. - [ ] E-posta ve şifre alanlı,
Form+GlobalKey<FormState>kullanan bir giriş formu yaz, her iki alan için devalidatorekle. - [ ] Boş bir alan veya geçersiz bir e-posta ile "Gönder"e basmayı dene, hata mesajlarının nasıl göründüğünü gözlemle.
- [ ]
obscureText: truekullanarak bir şifre alanı oluştur, görsel farkı gözlemle. - [ ] Form geçerli olduğunda, tüm controller'lardaki değerleri
printile ekrana yazdıran bir "kayıt ol" akışı tamamla.
Sıradaki konu: Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)