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:
validate(): "Hey Form, içindeki tüm alanları kontrol et, kurallara uymayan var mı?"save(): "Her şey yolundaysa, içindeki verileri değişkenlere kaydet."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.
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.
@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.
// 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.
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.
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:
onSaved: Yukarıdaki örnekte kullandığımız yöntem. Form submit edildiğinde veriyi alırsınız. Daha az kod gerektirir (boilerplate free).
Controller: Her
TextFormField'a birTextEditingControlleratarsı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ızonSavedyeterlidir.
GlobalKey Performansı
GlobalKey oluşturmak Flutter için nispeten maliyetli bir işlemdir.
Gereksiz yere
GlobalKeyoluş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:
_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:
Formwidget’ı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?
| Senaryo | Form 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
Yorum Gönder