Flutter'da Form Yönetimi ve GlobalKey Kullanımı

Flutter'da kullanıcıdan veri almak (login, kayıt, profil düzenleme vb.) en temel ihtiyaçlardan biridir ve bunu Form widget'ı ile GlobalKey kullanarak yapmak, verileri yönetmenin en "Flutter-cası" ve en temiz yoludur.


Flutter uygulamalarında tekil TextField widget'larını yönetmek kolaydır, ancak bir "Kayıt Ol" ekranı gibi birden fazla girdinin, doğrulamanın (validation) ve veri kaydının olduğu durumlarda işler karmaşıklaşabilir. İşte burada Form widget'ı ve onun kontrolcüsü olan GlobalKey<FormState> devreye girer.

Bu makalede, bu yapının nasıl çalıştığını, neden gerekli olduğunu ve profesyonel bir şekilde nasıl uygulanacağını adım adım inceleyeceğiz.

1. Temel Kavramlar: Form ve GlobalKey Nedir?

Kodlamaya geçmeden önce bu iki kavramın rollerini anlamalıyız.

Form Widget'ı

Birden fazla form alanını (TextFormField) gruplayan bir kapsayıcıdır (container). Tıpkı bir HTML formu gibi davranır. İçindeki tüm elemanları tek bir yerden yönetmenizi sağlar.

GlobalKey<FormState>

Bu, Form'un "Uzaktan Kumandasıdır".

Flutter'da widget ağacı hiyerarşiktir. Siz kodunuzun içinden (örneğin bir butona tıklandığında), Form widget'ının o anki durumuna (State) erişmek istersiniz. GlobalKey, formun içine erişmenizi ve şu kritik komutları vermenizi sağlar:

  1. validate(): "Hey Form, içindeki tüm alanları kontrol et, kurallara uymayan var mı?"

  2. save(): "Her şey yolundaysa, içindeki verileri değişkenlere kaydet."

  3. reset(): "Formu temizle."


2. Adım Adım Uygulama

Basit bir "Giriş Yap" (Login) senaryosu üzerinden gidelim. Bir E-posta ve Şifre alanı olacak.

Adım 1: Değişkenleri ve Anahtarı Tanımlama

Form durumu değişeceği için mutlaka StatefulWidget kullanmalıyız.

Dart
class LoginForm extends StatefulWidget {
  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  // 1. Form Anahtarını (Uzaktan Kumanda) oluşturuyoruz.
  // Bu anahtar FormState türünde bir durumu tutacak.
  final _formKey = GlobalKey<FormState>();

  // 2. Verileri tutacak değişkenler
  String? _email;
  String? _password;

  @override
  Widget build(BuildContext context) {
    // Birazdan burayı dolduracağız...
    return Scaffold(body: Container()); 
  }
}

Adım 2: Form Widget'ını Kurma

Form widget'ını oluşturup key parametresine yukarıda oluşturduğumuz _formKey'i veriyoruz. Bu, kumandayı televizyona tanıtmak gibidir.

Dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Form Eğitimi")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey, // Anahtarı buraya bağlıyoruz!
          child: Column(
            children: [
              // Form elemanları buraya gelecek
            ],
          ),
        ),
      ),
    );
  }

Adım 3: TextFormField ve Validation (Doğrulama)

Normal TextField yerine TextFormField kullanıyoruz. Çünkü TextFormField, Form widget'ı ile otomatik olarak konuşabilen özel yeteneklere (validator, onSaved) sahiptir.

Validator Mantığı:

  • Fonksiyon bir String? döndürür.

  • Eğer null dönerse: "Hata yok, geçerli" demektir.

  • Eğer Metin dönerse: "Hata var" demektir ve o metni hata mesajı olarak ekrana basar.

Dart
// E-posta Alanı
TextFormField(
  decoration: InputDecoration(labelText: "E-Posta"),
  keyboardType: TextInputType.emailAddress,
  
  // DOĞRULAMA MANTIĞI
  validator: (value) {
    if (value == null || value.isEmpty) {
      return "Lütfen e-posta adresinizi girin";
    }
    if (!value.contains('@')) {
      return "Geçerli bir e-posta adresi değil";
    }
    return null; // Her şey yolunda
  },
  
  // KAYIT MANTIĞI
  onSaved: (value) {
    _email = value;
  },
),

SizedBox(height: 20),

// Şifre Alanı
TextFormField(
  decoration: InputDecoration(labelText: "Şifre"),
  obscureText: true, // Şifreyi gizle
  
  validator: (value) {
    if (value == null || value.length < 6) {
      return "Şifre en az 6 karakter olmalıdır";
    }
    return null;
  },
  
  onSaved: (value) {
    _password = value;
  },
),

Adım 4: Gönder Butonu ve GlobalKey Kullanımı

En kritik nokta burasıdır. Butona tıklandığında _formKey üzerinden formun durumunu kontrol edeceğiz.

Dart
ElevatedButton(
  onPressed: () {
    // 1. Formun içindeki 'validator' fonksiyonlarını tetikler.
    // Eğer hepsi 'null' dönerse, bu fonksiyon 'true' döner.
    if (_formKey.currentState!.validate()) {
      
      // 2. Doğrulama başarılıysa, tüm 'onSaved' metodlarını çalıştırır.
      _formKey.currentState!.save();

      // 3. Artık veriler değişkenlerimizde (_email, _password)
      print("Form Başarılı! Email: $_email, Şifre: $_password");
      
      // Burada API isteği atabilirsiniz.
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Giriş yapılıyor...')),
      );
    }
  },
  child: Text("Giriş Yap"),
)

3. Tam Kod Örneği (Kopyala/Yapıştır)

Aşağıdaki kodu direkt olarak main.dart dosyanıza yapıştırıp çalıştırabilirsiniz.

Dart
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: LoginForm()));
}

class LoginForm extends StatefulWidget {
  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  // GlobalKey tanımı: FormState türünde
  final _formKey = GlobalKey<FormState>();

  // Verileri tutacak değişkenler
  String? _email;
  String? _password;

  void _submitForm() {
    // currentState'in null olmadığından emin oluyoruz (!)
    if (_formKey.currentState!.validate()) {
      
      // Form geçerliyse verileri kaydet
      _formKey.currentState!.save();

      // İşlem başarılı simülasyonu
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text("Başarılı"),
          content: Text("Email: $_email\nŞifre: $_password"),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: Text("Tamam"),
            )
          ],
        ),
      );
    } else {
      print("Formda hatalar var.");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("GlobalKey Form Örneği")),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Form(
          key: _formKey, // Anahtarı forma atadık
          // AutovalidateMode: Kullanıcı etkileşime girdiğinde hataları göster
          autovalidateMode: AutovalidateMode.onUserInteraction, 
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextFormField(
                decoration: InputDecoration(
                  labelText: "E-Posta",
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.email),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "E-posta boş olamaz";
                  }
                  if (!value.contains('@')) {
                    return "Geçersiz format";
                  }
                  return null;
                },
                onSaved: (value) => _email = value,
              ),
              SizedBox(height: 20),
              TextFormField(
                decoration: InputDecoration(
                  labelText: "Şifre",
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.lock),
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.length < 6) {
                    return "Şifre en az 6 karakter olmalı";
                  }
                  return null;
                },
                onSaved: (value) => _password = value,
              ),
              SizedBox(height: 30),
              SizedBox(
                width: double.infinity,
                height: 50,
                child: ElevatedButton(
                  onPressed: _submitForm,
                  child: Text("Giriş Yap", style: TextStyle(fontSize: 18)),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

4. İpuçları ve En İyi Uygulamalar (Best Practices)

AutovalidateMode Kullanımı

Kullanıcıya hatayı ne zaman göstereceğiniz önemlidir.

  • AutovalidateMode.disabled: Sadece butona basınca hata gösterir (Varsayılan).

  • AutovalidateMode.onUserInteraction: (Önerilen) Kullanıcı yazmaya başladığı veya alandan çıktığı an hatayı gösterir. Daha iyi bir UX sağlar.

  • AutovalidateMode.always: Form açıldığı an hataları gösterir (Genelde istenmez).

TextEditingController vs onSaved

Form verilerini almak için iki yöntem vardır:

  1. onSaved: Yukarıdaki örnekte kullandığımız yöntem. Form submit edildiğinde veriyi alırsınız. Daha az kod gerektirir (boilerplate free).

  2. Controller: Her TextFormField'a bir TextEditingController atarsınız. Eğer anlık olarak kullanıcının yazdığına müdahale etmeniz gerekiyorsa (örneğin yazdıkça arama yapma) Controller kullanın. Sadece son veriyi alacaksanız onSaved yeterlidir.

GlobalKey Performansı

GlobalKey oluşturmak Flutter için nispeten maliyetli bir işlemdir.

  • Gereksiz yere GlobalKey oluşturmayın.

  • Aynı key'i farklı widget'larda aynı anda kullanmaya çalışmayın (Duplicate Key hatası alırsınız).

Form Resetleme

Kayıt işleminden sonra formu temizlemek isterseniz:

Dart
_formKey.currentState!.reset();

Bu komut alanları boşaltır ve hata mesajlarını siler.

Sonuç

Form ve GlobalKey ikilisi, Flutter'da veri girişini yönetmenin standart ve güvenilir yoludur. Kodunuzu temiz tutar, doğrulama mantığını UI'dan kısmen ayırır ve kullanıcıya anında geri bildirim vermenizi kolaylaştırır.


Flutter’da kullanıcıdan veri alırken sadece TextField kullanmak yeterli değildir.
Gerçek uygulamalarda:

  • Doğrulama (validation)

  • Formu topluca kontrol etme

  • Kaydetme (save)

  • Sıfırlama (reset)

işlemlerine ihtiyaç duyarız.

İşte burada devreye:

  • Form widget’ı

  • GlobalKey<FormState>

girer.


🧱 1️⃣ Form Nedir?

Form, birden fazla input alanını tek bir çatı altında toplar ve onların durumunu yönetir.

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(),
      TextFormField(),
    ],
  ),
)

Form tek başına bir şey yapmaz.
Asıl güç FormState üzerinden gelir.


🔑 2️⃣ GlobalKey Nedir ve Neden Kullanılır?

Normalde widget’lara dışarıdan erişemeyiz.
Ama GlobalKey sayesinde bir widget’ın state’ine ulaşabiliriz.

final _formKey = GlobalKey<FormState>();

Bu key’i Form’a bağlarız:

Form(
  key: _formKey,
  child: ...
)

Artık şunları yapabiliriz:

_formKey.currentState!.validate();
_formKey.currentState!.save();
_formKey.currentState!.reset();

🧠 3️⃣ FormState Metotları

✅ validate()

Tüm TextFormField validator’larını çalıştırır.

if (_formKey.currentState!.validate()) {
  print("Form geçerli");
}

💾 save()

Her TextFormField içindeki onSaved fonksiyonunu tetikler.

onSaved: (value) {
  email = value!;
}

🔄 reset()

Formu sıfırlar.

_formKey.currentState!.reset();

🧪 4️⃣ Tam Örnek – Login Formu (Material 3 Uyumlu)

import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();

  String email = "";
  String password = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Login")),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                decoration: const InputDecoration(
                  labelText: "Email",
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "Email boş bırakılamaz";
                  }
                  if (!value.contains("@")) {
                    return "Geçerli email giriniz";
                  }
                  return null;
                },
                onSaved: (value) => email = value!,
              ),
              const SizedBox(height: 16),
              TextFormField(
                decoration: const InputDecoration(
                  labelText: "Şifre",
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.length < 6) {
                    return "Şifre en az 6 karakter olmalı";
                  }
                  return null;
                },
                onSaved: (value) => password = value!,
              ),
              const SizedBox(height: 24),
              FilledButton(
                onPressed: () {
                  if (_formKey.currentState!.validate()) {
                    _formKey.currentState!.save();

                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text("Giriş başarılı: $email"),
                      ),
                    );
                  }
                },
                child: const Text("Giriş Yap"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

🎨 5️⃣ Material 3 Ayarları

main.dart içinde:

MaterialApp(
  theme: ThemeData(
    useMaterial3: true,
    colorSchemeSeed: Colors.blue,
  ),
)

Bu sayede:

  • FilledButton modern görünür

  • Input alanları M3 uyumlu olur

  • SnackBar daha modern tasarım alır


📊 6️⃣ Form Veri Akış Diyagramı

Kullanıcı Input Girer
        ↓
TextFormField.validator()
        ↓
Form.validate()
        ↓
Form.save()
        ↓
Değişkenlere Aktarım
        ↓
Backend / İşlem

🧼 7️⃣ Clean Code Versiyon (Widget’lara Bölünmüş)

Büyük projelerde:

  • LoginForm widget

  • CustomTextField widget

  • SubmitButton widget

şeklinde ayırmak gerekir.

Örnek yapı:

lib/
 ├── pages/
 │    └── login_page.dart
 ├── widgets/
 │    ├── custom_textfield.dart
 │    └── submit_button.dart

Bu yapı:

✔ okunabilirliği artırır
✔ test edilebilirliği artırır
✔ yeniden kullanılabilirlik sağlar


⚠️ 8️⃣ Sık Yapılan Hatalar

❌ Form olmadan validate çağırmak

❌ GlobalKey’i build içinde oluşturmak

❌ onSaved kullanmadan save() çağırmak

❌ currentState! null kontrolü yapmamak


🧠 9️⃣ Ne Zaman Form Kullanmalıyız?

SenaryoForm Gerekli mi?
Login
Register
Profil Güncelleme
Tek input arama

🎯 Özet

Flutter’da profesyonel veri alma yapısı:

Form
 + GlobalKey
 + TextFormField
 + validator
 + onSaved

Bu yapı olmadan büyük uygulama geliştirmek mümkün değildir.



Yorumlar

Bu blogdaki popüler yayınlar

Dart Uygulama Sınavı: Pardus ETAP 23 Kurulum Otomasyonu

Dart Programlama Dil Uygulama Sınavı Çalışma Soruları

Pardus Üzerinde Flutter Geliştirme Ortamı Kurulumu