↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon

3 dk okuma #flutter
Dizi · 15/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. TextField — En Temel Metin Girişi
  2. TextEditingController — Girdiye Programatik Erişim
  3. Form ve TextFormField — Yapılandırılmış Form Yönetimi
  4. GlobalKey<FormState> — Ne İşe Yarıyor?
  5. validator Fonksiyonu — Dart Bilginle Çözümleme
  6. Birden Fazla Alan ile Tam Bir Form
  7. 🎯 Bu Dersten Çıkarılması Gerekenler
  8. 📝 Ödevler

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

dart
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

dart
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:

dart
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.

dart
final _formKey = GlobalKey<FormState>(); // GENERİC! Dart Bölüm 9'u hatırla

GlobalKey<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ı).

dart
Form(
  key: _formKey, // bu anahtarı Form widget'ına "veriyoruz"
  child: ...,
)
dart
_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

dart
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:

  • null döndürürse → "hata yok, geçerli."
  • Bir String dö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

dart
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ı).
  • validator fonksiyonu, null (geçerli) veya bir String (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

  • [ ] TextEditingController ile bir TextField'ın girdisini bir butona basınca print ile yazdır.
  • [ ] E-posta ve şifre alanlı, Form + GlobalKey<FormState> kullanan bir giriş formu yaz, her iki alan için de validator ekle.
  • [ ] 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: true kullanarak bir şifre alanı oluştur, görsel farkı gözlemle.
  • [ ] Form geçerli olduğunda, tüm controller'lardaki değerleri print ile ekrana yazdıran bir "kayıt ol" akışı tamamla.

Sıradaki konu: Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)