Flutter Input Widget'ları ve Özellikleri

Flutter'da kullanıcı etkileşiminin temel taşı olan Input (Girdi) widget'ları hakkında kapsamlı, teknik ve pratik, temel metin girişlerinden karmaşık seçim araçlarına kadar en sık kullanılan bileşenleri ve özellikler.


Flutter, kullanıcıdan veri almak için Google'ın "Material Design" ve Apple'ın "Cupertino" standartlarına uygun çok zengin bir widget kütüphanesi sunar. Bu widget'lar sadece veri girişi sağlamakla kalmaz, aynı zamanda validasyon (doğrulama), biçimlendirme ve kullanıcı deneyimi (UX) yönetimi için güçlü özellikler barındırır.

İşte kategorilerine göre ayrılmış en önemli Flutter Input Widget'ları:

1. Metin Giriş Widget'ları (Text Inputs)

Metin girişi, formların en temel parçasıdır. Flutter'da bunun için iki ana widget bulunur: TextField ve TextFormField.

A. TextField

Kullanıcıdan basit metin girişi almak için kullanılan en temel widget'tır. Genellikle form validasyonu gerektirmeyen arama çubukları veya sohbet ekranlarında kullanılır.

Öne Çıkan Özellikler:

  • controller: Girilen metni okumak, değiştirmek veya silmek için kullanılan TextEditingController nesnesidir.

  • onChanged: Metin her değiştiğinde tetiklenen fonksiyondur.

  • obscureText: true yapıldığında metni gizler (Şifre alanları için).

  • keyboardType: Klavyenin türünü belirler (Örn: TextInputType.emailAddress, TextInputType.number).

  • decoration: InputDecoration sınıfı ile çerçeve, ikon, etiket (label) ve ipucu (hint) metinleri eklenir.

B. TextFormField

TextField'ın sarmalanmış ve geliştirilmiş halidir. Bir Form widget'ı içinde kullanıldığında otomatik doğrulama (validation) yapabilir.

Kritik Özellikler:

  • validator: Girilen veriyi kontrol eden fonksiyondur. Eğer veri hatalıysa bir hata mesajı (String), doğruysa null döndürür.

  • onSaved: Form kaydedildiğinde çalışacak fonksiyondur.

Örnek Kod (TextFormField):

Dart:
TextFormField(
  decoration: InputDecoration(
    labelText: "E-Posta",
    border: OutlineInputBorder(),
    prefixIcon: Icon(Icons.email),
  ),
  keyboardType: TextInputType.emailAddress,
  validator: (value) {
    if (value == null || !value.contains('@')) {
      return 'Geçerli bir mail giriniz';
    }
    return null;
  },
);

2. Seçim Widget'ları (Selection Inputs)

Kullanıcıya belirli seçenekler sunmak için kullanılır.

A. Checkbox (Onay Kutusu)

Genellikle "Evet/Hayır" veya çoklu seçim durumlarında kullanılır (Örn: "Kullanım koşullarını kabul ediyorum").

  • value: true (seçili) veya false (seçili değil) durumunu tutar.

  • tristate: true yapılırsa, kutucuk boş (null) değer de alabilir.

  • onChanged: Durum değiştiğinde tetiklenir.

B. Radio (Radyo Butonu)

Bir grup seçenek arasından sadece birini seçtirmek için kullanılır (Örn: Cinsiyet seçimi).

  • value: O butonun temsil ettiği değer.

  • groupValue: O an seçili olan grubun değeri. Bu iki değer eşleştiğinde buton seçili görünür.

C. Switch (Anahtar)

Ayarları açıp kapatmak için kullanılan, kaydırmalı bir düğmedir.

  • Switch.adaptive: Bu kurucuyu (constructor) kullanırsanız, uygulama iOS'ta çalışırken iOS tarzı, Android'de çalışırken Android tarzı switch gösterir. Bu, platforma özgü his için çok önemlidir.


3. Liste ve Açılır Menüler (Dropdown & Autocomplete)

Uzun listelerden veri seçimi için kullanılır.

A. DropdownButton / DropdownMenu

Kullanıcının tıklayarak açtığı ve bir listeden seçim yaptığı klasik menüdür.

  • items: Seçeneklerin listesidir (DropdownMenuItem widget'ları).

  • value: O an seçili olan değer.

  • onChanged: Yeni bir seçim yapıldığında çalışır.

B. Autocomplete (Otomatik Tamamlama)

Kullanıcı yazmaya başladığında, girilen metne uygun önerileri listeleyen widget'tır. Çok uzun listeler (Örn: Şehir seçimi) için idealdir.


4. Sayısal ve Aralık Seçimi (Sliders)

Kullanıcının bir aralık üzerinden değer seçmesini sağlar.

A. Slider

Tek bir değeri (Örn: Ses seviyesi, Ekran parlaklığı) seçmek için kullanılır.

  • min / max: Kaydırıcının başlangıç ve bitiş değerleri.

  • divisions: Kaydırıcının kaç parçaya bölüneceği (tık tık ilerlemesi için).

B. RangeSlider

İki değer arasındaki bir aralığı seçmek için kullanılır (Örn: Fiyat filtresi - Min: 100 TL, Max: 500 TL).


5. Tarih ve Saat Seçiciler (Pickers)

Flutter, tarih ve saat seçimi için hazır diyalog pencereleri sunar. Bunlar doğrudan widget olarak değil, bir fonksiyon çağrısı ile ekrana gelir.

  • showDatePicker(): Takvim arayüzü açar.

  • showTimePicker(): Saat kadranı veya giriş arayüzü açar.


6. Form Yönetimi (Form Widget)

Tüm bu inputları bir arada yönetmek, hepsini aynı anda doğrulamak (validate) ve verilerini kaydetmek için Form widget'ı kullanılır.

Temel Yapı:

  1. Bir GlobalKey<FormState> oluşturulur.

  2. Inputlar Form widget'ı ile sarmalanır.

  3. Bir butona basıldığında _formKey.currentState!.validate() çağrılır.

Tablo: Hangi Widget Ne Zaman Kullanılmalı?

WidgetKullanım SenaryosuAnahtar Özellik
TextFieldBasit, validasyon gerektirmeyen girişler (Chat, Arama).controller
TextFormFieldKayıt formları, Login ekranları (Validasyonlu).validator
SwitchAyarlar ekranı, Aç/Kapa işlemleri.adaptive
RadioBirbirini dışlayan tekli seçimler.groupValue
DropdownButtonŞehir, Ülke gibi liste seçimleri.items

İpucu: Tasarım Özelleştirme (InputDecoration)

Tüm metin tabanlı inputlar InputDecoration ile özelleştirilir. En sık kullanılan tasarım özellikleri şunlardır:

  • filled: true ise arka planı renklendirir.

  • fillColor: Arka plan rengi.

  • border: Kenarlık tipi (Örn: OutlineInputBorder, UnderlineInputBorder).

  • enabledBorder: Odaklanılmamış (boşta) durumdaki kenarlık.

  • focusedBorder: Üzerine tıklandığındaki kenarlık.


Mobil uygulamalarda kullanıcıyla etkileşimin temelini input (girdi) widget’ları oluşturur. Flutter, kullanıcıdan metin, seçim, onay, sayı, tarih gibi birçok veri alabilmek için zengin ve özelleştirilebilir widget’lar sunar.


🔹 1. TextField

📌 Kullanıcının metin girmesini sağlar

🧩 Temel Kullanım

TextField(
  decoration: InputDecoration(
    labelText: "Ad Soyad",
    hintText: "Adınızı giriniz",
    border: OutlineInputBorder(),
  ),
)

⚙️ Önemli Özellikler

ÖzellikAçıklama
controllerGirilen veriyi okumak
keyboardTypeKlavye türü (text, number, email)
obscureTextŞifre gizleme
maxLengthMaksimum karakter
onChangedAnlık değişimi yakalar

🔐 Şifre Alanı Örneği

TextField(
  obscureText: true,
  decoration: InputDecoration(
    labelText: "Şifre",
  ),
)

🔹 2. TextFormField

📌 Form yapıları için gelişmiş TextField

Form doğrulama (validation) gereken durumlarda kullanılır.

TextFormField(
  validator: (value) {
    if (value!.isEmpty) {
      return "Bu alan boş bırakılamaz";
    }
    return null;
  },
)

🆚 TextField vs TextFormField

ÖzellikTextFieldTextFormField
Validation
Form uyumu
KontrolOrtaGelişmiş

🔹 3. Checkbox

📌 Evet / Hayır – Kabul ediyorum gibi durumlar

bool kabul = false;

Checkbox(
  value: kabul,
  onChanged: (value) {
    setState(() {
      kabul = value!;
    });
  },
)

🧠 Kullanım Alanları

  • KVKK onayı

  • Kullanım şartları

  • Tercih işaretleme


🔹 4. Switch

📌 Açık / Kapalı (ON / OFF) kontrolü

Switch(
  value: isDarkMode,
  onChanged: (value) {
    setState(() {
      isDarkMode = value;
    });
  },
)

💡 Nerelerde Kullanılır?

  • Tema değişimi

  • Bildirim aç/kapat

  • Ayarlar ekranları


🔹 5. Radio & RadioListTile

📌 Tek seçenekli seçim

Radio(
  value: 1,
  groupValue: secim,
  onChanged: (value) {
    setState(() {
      secim = value;
    });
  },
)

🧩 Daha Kullanışlı: RadioListTile

RadioListTile(
  title: Text("Erkek"),
  value: "E",
  groupValue: cinsiyet,
  onChanged: (value) {
    setState(() {
      cinsiyet = value!;
    });
  },
)

🔹 6. DropdownButton

📌 Açılır liste ile seçim

DropdownButton<String>(
  value: secilenSehir,
  items: ["Ankara", "İstanbul", "İzmir"]
      .map((sehir) => DropdownMenuItem(
            value: sehir,
            child: Text(sehir),
          ))
      .toList(),
  onChanged: (value) {
    setState(() {
      secilenSehir = value!;
    });
  },
)

🔹 7. Slider

📌 Değer aralığı seçimi

Slider(
  value: yas,
  min: 0,
  max: 100,
  divisions: 100,
  label: yas.toString(),
  onChanged: (value) {
    setState(() {
      yas = value;
    });
  },
)

🎯 Kullanım Alanları

  • Ses seviyesi

  • Yaş / puan

  • Parlaklık ayarı


🔹 8. DatePicker & TimePicker

📌 Tarih ve saat seçimi

showDatePicker(
  context: context,
  initialDate: DateTime.now(),
  firstDate: DateTime(2000),
  lastDate: DateTime(2100),
);

🔹 9. Form & GlobalKey

📌 Tüm inputları tek form altında yönetme

final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(),
      ElevatedButton(
        onPressed: () {
          if (_formKey.currentState!.validate()) {
            print("Form geçerli");
          }
        },
        child: Text("Gönder"),
      ),
    ],
  ),
)

📌 En Çok Kullanılan Input Widget’ları (Özet)

WidgetAmaç
TextFieldMetin girişi
TextFormFieldDoğrulamalı metin
CheckboxOnay
SwitchAç / Kapat
RadioTek seçim
DropdownListe
SliderDeğer aralığı
DatePickerTarih

🎓 Eğitimde & Projelerde Nerede Kullanılır?

  • 🧾 Kayıt / Giriş ekranları

  • 🏥 Sağlık uygulamaları (boy, kilo, yaş)

  • ⚙️ Ayarlar menüsü

  • 📊 Anket & form uygulamaları


🚀 Sonuç

Flutter input widget’ları:

  • Esnek

  • Özelleştirilebilir

  • Modern UI uyumlu

  • Material 3 ile güçlü

Bu yapı taşlarını iyi öğrenmek, profesyonel Flutter uygulamaları geliştirmenin temelidir.


Flutter input widget’ları kullanılarak hazırlanmış, baştan sona çalışan TAM BİR FORM PROJESİ 

Bu proje  laboratuvar uygulaması, gerçek hayatta kullanılan kayıt formu mantığında tasarlanmıştır.


📱 Flutter Form Projesi

(Input Widget’larıyla Kapsamlı Kullanıcı Kayıt Formu)


🎯 Proje Özeti

Bu projede kullanıcıdan şu bilgiler alınacaktır:

  • Ad Soyad (TextFormField)

  • E-posta (TextFormField – validation)

  • Şifre (TextFormField – gizli)

  • Cinsiyet (RadioListTile)

  • Şehir (DropdownButtonFormField)

  • Yaş (Slider)

  • Şartları kabul et (Checkbox)

  • Gönder (ElevatedButton)

Form doğrulama (validation) içerecek ve hatalı girişlerde uyarı verecek.


📁 Proje Yapısı

lib/
 └── main.dart

🧱 1. main.dart (TAM KOD)

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Form Projesi',
      home: const FormSayfasi(),
    );
  }
}

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

  @override
  State<FormSayfasi> createState() => _FormSayfasiState();
}

class _FormSayfasiState extends State<FormSayfasi> {
  final _formKey = GlobalKey<FormState>();

  final TextEditingController adController = TextEditingController();
  final TextEditingController emailController = TextEditingController();
  final TextEditingController sifreController = TextEditingController();

  String cinsiyet = "Erkek";
  String sehir = "Ankara";
  double yas = 18;
  bool kabulEdildi = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Kullanıcı Kayıt Formu"),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: ListView(
            children: [

              /// AD SOYAD
              TextFormField(
                controller: adController,
                decoration: const InputDecoration(
                  labelText: "Ad Soyad",
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value!.isEmpty) {
                    return "Ad Soyad boş bırakılamaz";
                  }
                  return null;
                },
              ),

              const SizedBox(height: 15),

              /// E-POSTA
              TextFormField(
                controller: emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  labelText: "E-posta",
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (!value!.contains("@")) {
                    return "Geçerli bir e-posta giriniz";
                  }
                  return null;
                },
              ),

              const SizedBox(height: 15),

              /// ŞİFRE
              TextFormField(
                controller: sifreController,
                obscureText: true,
                decoration: const InputDecoration(
                  labelText: "Şifre",
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value!.length < 6) {
                    return "Şifre en az 6 karakter olmalı";
                  }
                  return null;
                },
              ),

              const SizedBox(height: 20),

              /// CİNSİYET
              const Text("Cinsiyet", style: TextStyle(fontWeight: FontWeight.bold)),
              RadioListTile(
                title: const Text("Erkek"),
                value: "Erkek",
                groupValue: cinsiyet,
                onChanged: (value) {
                  setState(() {
                    cinsiyet = value!;
                  });
                },
              ),
              RadioListTile(
                title: const Text("Kadın"),
                value: "Kadın",
                groupValue: cinsiyet,
                onChanged: (value) {
                  setState(() {
                    cinsiyet = value!;
                  });
                },
              ),

              /// ŞEHİR
              DropdownButtonFormField<String>(
                value: sehir,
                decoration: const InputDecoration(
                  labelText: "Şehir",
                  border: OutlineInputBorder(),
                ),
                items: ["Ankara", "İstanbul", "İzmir", "Bursa"]
                    .map((item) => DropdownMenuItem(
                          value: item,
                          child: Text(item),
                        ))
                    .toList(),
                onChanged: (value) {
                  setState(() {
                    sehir = value!;
                  });
                },
              ),

              const SizedBox(height: 20),

              /// YAŞ
              Text("Yaş: ${yas.toInt()}"),
              Slider(
                value: yas,
                min: 10,
                max: 80,
                divisions: 70,
                label: yas.toInt().toString(),
                onChanged: (value) {
                  setState(() {
                    yas = value;
                  });
                },
              ),

              /// KABUL
              CheckboxListTile(
                title: const Text("Kullanım şartlarını kabul ediyorum"),
                value: kabulEdildi,
                onChanged: (value) {
                  setState(() {
                    kabulEdildi = value!;
                  });
                },
              ),

              const SizedBox(height: 20),

              /// GÖNDER BUTONU
              ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState!.validate() && kabulEdildi) {
                    showDialog(
                      context: context,
                      builder: (_) => AlertDialog(
                        title: const Text("Başarılı"),
                        content: Text(
                          "Kayıt tamamlandı!\n\n"
                          "Ad: ${adController.text}\n"
                          "E-posta: ${emailController.text}\n"
                          "Cinsiyet: $cinsiyet\n"
                          "Şehir: $sehir\n"
                          "Yaş: ${yas.toInt()}",
                        ),
                      ),
                    );
                  } else if (!kabulEdildi) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(
                        content: Text("Şartları kabul etmelisiniz"),
                      ),
                    );
                  }
                },
                child: const Text("KAYIT OL"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

🧪 Bu Projede Öğrenilenler

✔ TextFormField & validation
✔ Controller kullanımı
✔ Radio / Dropdown / Slider
✔ Checkbox kontrolü
✔ Form + GlobalKey
✔ Snackbar & AlertDialog
✔ Gerçek kayıt ekranı mantığı


🎓 LAB ÇALIŞMASI ÖNERİSİ (Öğrenciye)

  1. Şifreyi en az 1 büyük harf şartı ekle

  2. Şehir listesini 10 il yap

  3. Yaş 18’den küçükse uyarı ver

  4. Tema rengini değiştir

  5. Firebase’e kayıt ekle (ileri seviye)


Aşağıda, önceki bölümde bahsettiğimiz widget'ları bir araya getiren, doğrulama (validation) mantığına sahip ve gerçek bir uygulama senaryosunu simüle eden kapsamlı bir Kayıt Formu örneği hazırladım.

Bu kodu doğrudan projenizdeki main.dart dosyasına yapıştırıp çalıştırabilirsiniz.

📝 Tam Teşekküllü Kullanıcı Kayıt Formu

Bu örnek şunları içerir:

  1. TextFormField: İsim, E-posta ve Şifre (Gizli karakterli).

  2. DropdownButtonFormField: Şehir seçimi.

  3. RadioListTile: Cinsiyet seçimi.

  4. CheckboxListTile: Kullanıcı sözleşmesi onayı.

  5. Slider: Yaş seçimi.

  6. Form & GlobalKey: Tüm verileri tek seferde kontrol etme mekanizması.

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

void main() {
  runApp(const MaterialApp(home: KayitEkrani()));
}

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

  @override
  State<KayitEkrani> createState() => _KayitEkraniState();
}

class _KayitEkraniState extends State<KayitEkrani> {
  // 1. Form Anahtarı: Formun durumunu (validasyon, kaydetme) yönetmek için gereklidir.
  final _formKey = GlobalKey<FormState>();

  // Controller'lar: Metin alanlarındaki veriyi okumak için.
  final TextEditingController _isimController = TextEditingController();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _sifreController = TextEditingController();

  // Değişkenler: Seçim widget'larının durumunu tutmak için.
  String? _secilenSehir;
  String? _cinsiyet = 'Belirtilmedi';
  bool _sozlesmeKabul = false;
  double _yas = 18;
  bool _sifreGoster = false;

  final List<String> _sehirler = ['İstanbul', 'Ankara', 'İzmir', 'Bursa', 'Antalya'];

  // Formu Gönder Fonksiyonu
  void _kayitOl() {
    // _formKey.currentState!.validate() tüm validator fonksiyonlarını çalıştırır.
    if (_formKey.currentState!.validate()) {
      // Validasyon başarılıysa burası çalışır.
      if (!_sozlesmeKabul) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Lütfen sözleşmeyi kabul ediniz!')),
        );
        return;
      }

      _formKey.currentState!.save(); // Formu kaydeder (onSaved tetiklenir)
      
      // Örnek bir başarı mesajı
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text("Kayıt Başarılı"),
          content: Text(
            "İsim: ${_isimController.text}\n"
            "Şehir: $_secilenSehir\n"
            "Yaş: ${_yas.toInt()}\n"
            "Cinsiyet: $_cinsiyet"
          ),
          actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text("Tamam"))],
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Yeni Kullanıcı Kaydı'), backgroundColor: Colors.teal),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        // Form Widget'ı tüm inputları sarmalar
        child: Form(
          key: _formKey,
          child: ListView(
            children: [
              // --- İSİM ALANI ---
              TextFormField(
                controller: _isimController,
                decoration: const InputDecoration(
                  labelText: 'Ad Soyad',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.person),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) return 'İsim boş bırakılamaz';
                  if (value.length < 3) return 'İsim en az 3 karakter olmalı';
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // --- E-POSTA ALANI ---
              TextFormField(
                controller: _emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  labelText: 'E-Posta',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.email),
                ),
                validator: (value) {
                  if (value == null || !value.contains('@')) return 'Geçerli bir e-posta giriniz';
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // --- ŞİFRE ALANI ---
              TextFormField(
                controller: _sifreController,
                obscureText: !_sifreGoster, // Şifreyi gizle/göster
                decoration: InputDecoration(
                  labelText: 'Şifre',
                  border: const OutlineInputBorder(),
                  prefixIcon: const Icon(Icons.lock),
                  suffixIcon: IconButton(
                    icon: Icon(_sifreGoster ? Icons.visibility : Icons.visibility_off),
                    onPressed: () {
                      setState(() {
                        _sifreGoster = !_sifreGoster;
                      });
                    },
                  ),
                ),
                validator: (value) {
                  if (value == null || value.length < 6) return 'Şifre en az 6 karakter olmalı';
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // --- ŞEHİR SEÇİMİ (DROPDOWN) ---
              DropdownButtonFormField<String>(
                decoration: const InputDecoration(
                  labelText: 'Şehir Seçiniz',
                  border: OutlineInputBorder(),
                ),
                value: _secilenSehir,
                items: _sehirler.map((sehir) {
                  return DropdownMenuItem(value: sehir, child: Text(sehir));
                }).toList(),
                onChanged: (value) => setState(() => _secilenSehir = value),
                validator: (value) => value == null ? 'Lütfen bir şehir seçiniz' : null,
              ),
              const SizedBox(height: 20),

              // --- YAŞ SEÇİMİ (SLIDER) ---
              Text("Yaşınız: ${_yas.toInt()}", style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
              Slider(
                value: _yas,
                min: 18,
                max: 90,
                divisions: 72,
                label: _yas.toInt().toString(),
                onChanged: (double value) {
                  setState(() {
                    _yas = value;
                  });
                },
              ),

              // --- CİNSİYET SEÇİMİ (RADIO) ---
              const Text("Cinsiyet", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
              Row(
                children: [
                  Expanded(
                    child: RadioListTile<String>(
                      title: const Text('Kadın'),
                      value: 'Kadın',
                      groupValue: _cinsiyet,
                      onChanged: (value) => setState(() => _cinsiyet = value),
                    ),
                  ),
                  Expanded(
                    child: RadioListTile<String>(
                      title: const Text('Erkek'),
                      value: 'Erkek',
                      groupValue: _cinsiyet,
                      onChanged: (value) => setState(() => _cinsiyet = value),
                    ),
                  ),
                ],
              ),

              // --- SÖZLEŞME ONAYI (CHECKBOX) ---
              CheckboxListTile(
                title: const Text("Kullanıcı sözleşmesini okudum ve kabul ediyorum."),
                value: _sozlesmeKabul,
                onChanged: (value) {
                  setState(() {
                    _sozlesmeKabul = value ?? false;
                  });
                },
                controlAffinity: ListTileControlAffinity.leading, // Kutucuğu başa alır
              ),

              const SizedBox(height: 20),

              // --- KAYIT OL BUTONU ---
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.teal,
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
                onPressed: _kayitOl,
                child: const Text('KAYIT OL', style: TextStyle(fontSize: 18, color: Colors.white)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

🔍 Kodun Önemli Noktaları

  1. GlobalKey (_formKey): Kodun beyni burasıdır. Butona basıldığında _formKey.currentState!.validate() komutu çalışır. Bu komut, form içindeki tüm TextFormField ve DropdownButtonFormField widget'larını tek tek gezer ve içlerindeki validator fonksiyonlarını çalıştırır. Eğer herhangi biri hata mesajı döndürürse işlemi durdurur ve hatayı ekrana basar.

  2. State Yönetimi (setState): Slider, Radio buton veya Checkbox gibi widget'ların durumları değiştiğinde (örneğin kutucuğa tıklandığında), ekranın yeniden çizilmesi ve tik işaretinin görünmesi için setState kullanmak zorunludur.

  3. Controller Kullanımı: Text alanlarındaki veriyi anlık olarak değil, genellikle butona basıldığında okuruz. _isimController.text diyerek kullanıcının girdiği veriye her yerden erişebiliriz.

  4. Kullanıcı Deneyimi (UX):

    • Şifre alanına bir "Göz" ikonu ekledik (suffixIcon). Buna basıldığında obscureText özelliği true/false arasında değişerek şifreyi gösterir veya gizler.

    • Klavye türlerini (keyboardType) veri tipine göre ayarladık (E-posta için @ işaretli klavye açılır).

Profesyonel uygulamalarda her TextFormField için tek tek decoration yazmak hem kod tekrarına yol açar hem de tasarımı değiştirmek istediğinizde (örneğin kenarlıkları daha yuvarlak yapmak istediğinizde) 50 farklı dosyayı değiştirmenizi gerektirir.

Bunun yerine, main.dart dosyasında merkezi bir Tema (Theme) tanımlayarak tüm uygulama genelindeki inputların görünümünü tek bir yerden yöneteceğiz.

🎨 Global Form Teması (ThemeData)

Aşağıdaki kodda, MaterialApp içindeki theme parametresini kullanarak modern, yumuşak hatlara sahip ve kullanıcı etkileşimine tepki veren bir tasarım oluşturuyoruz.

Bu kodu main.dart dosyanızdaki MaterialApp kısmına entegre edebilirsiniz:

Dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // Klavye işlemleri için

void main() {
  runApp(const ModernFormApp());
}

class ModernFormApp extends StatelessWidget {
  const ModernFormApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        // Ana renk paleti
        primarySwatch: Colors.indigo,
        
        // --- INPUT DECORATION THEME (Buraya Dikkat) ---
        // Tüm TextField ve TextFormField'lar otomatik olarak bu stili alacak
        inputDecorationTheme: InputDecorationTheme(
          filled: true, // Arka plan rengi olsun mu?
          fillColor: Colors.grey.shade100, // Hafif gri arka plan
          
          // İçerik ile kenarlar arasındaki boşluk (Daha ferah görünüm)
          contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
          
          // Varsayılan Kenarlık (Odaklanılmamış, Hata yok)
          enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12), // Yuvarlak köşeler
            borderSide: BorderSide(color: Colors.grey.shade300, width: 1.5),
          ),
          
          // Odaklanılmış Kenarlık (Kullanıcı yazarken)
          focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: const BorderSide(color: Colors.indigo, width: 2.5),
          ),
          
          // Hata Durumu Kenarlığı (Validasyon başarısızsa)
          errorBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: const BorderSide(color: Colors.redAccent, width: 2),
          ),
          
          // Hata varken odaklanılmış hali
          focusedErrorBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: const BorderSide(color: Colors.red, width: 2.5),
          ),
          
          // Etiket (Label) stili
          labelStyle: TextStyle(color: Colors.grey.shade600, fontWeight: FontWeight.w500),
          
          // İpucu (Hint) stili
          hintStyle: TextStyle(color: Colors.grey.shade400),
        ),
        
        // Buton Teması (Inputlarla uyumlu olsun diye)
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.indigo,
            foregroundColor: Colors.white,
            shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
            textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
      ),
      home: const KayitEkrani(), // Önceki kodunuzdaki sınıfı buraya çağırıyoruz
    );
  }
}

// ... KayitEkrani sınıfı ve altındaki kodlar buraya gelecek ...

🛠️ Kodunuzda Ne Değişmeli?

Artık KayitEkrani içindeki widget'larınızda stil tanımlarını silebilirsiniz. Kodunuz şu kadar temiz hale gelir:

Eski Hali (Karmaşık):

Dart
TextFormField(
  decoration: InputDecoration( // Her seferinde tekrar ediyor
    labelText: 'Ad Soyad',
    border: OutlineInputBorder(),
    filled: true,
    // ...vb
  ),
  //...
)

Yeni Hali (Temiz):

Dart
TextFormField(
  decoration: const InputDecoration(
    labelText: 'Ad Soyad',
    prefixIcon: Icon(Icons.person),
    // Stil özellikleri otomatik olarak main.dart'tan gelir!
  ),
  //...
)

💡 Tasarım Mantığı (Neden Böyle Yaptık?)

  1. Odak (Focus) Ayrımı: Kullanıcı hangi kutucuğa yazdığını net görmeli. Bu yüzden focusedBorder rengini ana renk (Indigo) yaptık ve kalınlığını artırdık.

  2. Hata Yönetimi: Kullanıcı yanlış bir şey girdiğinde errorBorder devreye girer ve çerçeve kırmızı olur. Kullanıcı hatayı düzeltmek için tıkladığında focusedErrorBorder çalışır.

  3. Filled (Dolu) Arka Plan: Modern mobil tasarımlarda (örneğin Instagram veya Twitter giriş ekranları), inputların arka planı genellikle hafif gridir (Colors.grey.shade100). Bu, input alanını beyaz sayfadan ayırır ve algıyı kolaylaştırır.

  4. BorderRadius: 12 piksellik yuvarlatma, sert köşeli "Windows 95" tarzı yerine modern bir mobil uygulama hissi verir.

Bu yapı sayesinde, yarın patronunuz "Uygulamamızı Mavi değil, Yeşil yapalım" derse, sadece main.dart içindeki renk kodunu değiştirmeniz yeterli olacaktır; tüm inputlar anında güncellenir.

Harika! Bir formun profesyonel hissettirmesi için en önemli detay akıcılıktır. Kullanıcı bir alanı doldurduktan sonra klavyedeki "İleri" tuşuna bastığında otomatik olarak bir sonraki kutucuğa geçilmeli ve boş bir yere tıklandığında klavye kapanmalıdır.

Bunu sağlamak için FocusNode (Odak Düğümü) ve GestureDetector kullanacağız.

İşte adım adım yapılacaklar:

1. Mantık: FocusNode Nedir?

Flutter'da her TextField bir odak noktasıdır. Biz bu noktaları manuel olarak yönetmek istiyorsak, her biri için bir "kumanda" (FocusNode) oluşturmalıyız.

2. Uygulama Adımları

Aşağıdaki kodda, önceki formumuzu şu özelliklerle güncelledim:

  1. İleri (Next) Tuşu: İsim girilip klavyedeki "İleri" tuşuna basılınca otomatik olarak E-posta kutusuna geçer.

  2. Tamam (Done) Tuşu: Şifre girilip "Tamam"a basılınca klavye kapanır.

  3. Boşluğa Tıklama: Kullanıcı form dışında boş bir yere tıklarsa klavye kapanır (Bu, mobil kullanıcıların en çok aradığı özelliktir).

İşte güncellenmiş KayitEkrani kodunuz:

Dart
// ... (Önceki importlar ve tanımlar aynı)

// Form içindeki ListView kısmını şu şekilde güncelleyin:

ListView(
  children: [
    // --- İSİM, E-POSTA, ŞİFRE KISIMLARI AYNI KALACAK ---
    TextFormField(
      controller: _isimController,
      textInputAction: TextInputAction.next,
      decoration: const InputDecoration(labelText: 'Ad Soyad', prefixIcon: Icon(Icons.person)),
    ),
    const SizedBox(height: 16),
    
    // ... (E-Posta ve Şifre alanları buraya gelecek - Değişiklik yok) ...

    // --- 1. DÜZELTME: DROPDOWN (initialValue kullanımı) ---
    DropdownButtonFormField<String>(
      decoration: const InputDecoration(
        labelText: 'Şehir Seçiniz',
        border: OutlineInputBorder(),
      ),
      // ESKİSİ: value: _secilenSehir,
      // YENİSİ: initialValue kullanılır
      initialValue: _secilenSehir, 
      items: _sehirler.map((sehir) {
        return DropdownMenuItem(value: sehir, child: Text(sehir));
      }).toList(),
      onChanged: (value) {
        setState(() {
          _secilenSehir = value;
        });
      },
      validator: (value) => value == null ? 'Lütfen bir şehir seçiniz' : null,
    ),
    const SizedBox(height: 20),

    // ... (Slider kısmı aynı kalacak) ...

    // --- 2. DÜZELTME: RADIO GROUP (Yeni Yöntem) ---
    const Text("Cinsiyet", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
    
    // Artık RadioListTile'ları RadioGroup ile sarmalıyoruz
    RadioGroup<String>(
      value: _cinsiyet, // Grubun değeri burada tutulur
      onChanged: (value) {
        setState(() {
          _cinsiyet = value;
        });
      },
      // RadioGroup içindeki çocuk widget'lar
      child: Row(
        children: [
          Expanded(
            child: RadioListTile<String>(
              title: const Text('Kadın'),
              value: 'Kadın',
              // groupValue ve onChanged BURADAN SİLİNDİ
              // Çünkü bunları artık üstteki RadioGroup yönetiyor
            ),
          ),
          Expanded(
            child: RadioListTile<String>(
              title: const Text('Erkek'),
              value: 'Erkek',
              // groupValue ve onChanged BURADAN SİLİNDİ
            ),
          ),
        ],
      ),
    ),

    // ... (Checkbox ve Buton kısıımları aynı kalacak) ...
  ],
)
  1. GestureDetector: Tüm sayfayı sarar. onTap özelliği sayesinde ekranda boş bir yere dokunulduğunda FocusScope.of(context).unfocus() çalışır. Bu komut, o an hangi kutucukta imleç varsa onu kaldırır ve klavyeyi aşağı indirir.

  2. textInputAction: Klavyenin sağ altındaki mavi butonu değiştirir.

    • TextInputAction.next: "İleri" oku çıkarır.

    • TextInputAction.done: "Tik" işareti (Bitti) çıkarır.

  3. onFieldSubmitted: Kullanıcı o mavi butona bastığında ne olacağını belirler. Biz burada bir sonraki FocusNode'a zıplamasını söyledik.

Bu yapı, kullanıcıların form doldurma hızını ciddi oranda artırır ve uygulamanızın kalitesini belli eder.

Aşağıda Flutter Material 3 (M3) tasarım anlayışına uygun, modern – sade – güncel UI kullanan tam bir Form tasarımı bulacaksın.

Bu örnek gerçek uygulama hissi verir, 2025+ Flutter standartlarına uygundur ve sınıf/lab anlatımında rahatça kullanılabilir.


🎨 Flutter Material 3 – Modern Form Tasarımı

(Input Widget’ları + Material You)


🎯 Tasarım Özellikleri (Material 3)

useMaterial3: true
ColorScheme.fromSeed()
✅ Rounded (yumuşak) köşeler
✅ Filled input alanları
✅ Icon destekli input’lar
✅ Modern butonlar
✅ Responsive (ListView)


📁 Proje Yapısı

lib/
 └── main.dart

🧱 main.dart – TAM MATERIAL 3 FORM KODU

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
        ),
      ),
      home: const ModernFormPage(),
    );
  }
}

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

  @override
  State<ModernFormPage> createState() => _ModernFormPageState();
}

class _ModernFormPageState extends State<ModernFormPage> {
  final _formKey = GlobalKey<FormState>();

  final adCtrl = TextEditingController();
  final emailCtrl = TextEditingController();
  final sifreCtrl = TextEditingController();

  String cinsiyet = "Erkek";
  String sehir = "Ankara";
  double yas = 18;
  bool sozlesme = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Kayıt Ol"),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: ListView(
            children: [

              /// BAŞLIK
              const Text(
                "Hesap Oluştur",
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Text(
                "Lütfen bilgilerinizi giriniz",
                style: TextStyle(
                  color: Theme.of(context).colorScheme.onSurfaceVariant,
                ),
              ),
              const SizedBox(height: 24),

              /// AD SOYAD
              TextFormField(
                controller: adCtrl,
                decoration: const InputDecoration(
                  labelText: "Ad Soyad",
                  prefixIcon: Icon(Icons.person),
                  filled: true,
                ),
                validator: (v) =>
                    v!.isEmpty ? "Bu alan boş bırakılamaz" : null,
              ),

              const SizedBox(height: 16),

              /// E-POSTA
              TextFormField(
                controller: emailCtrl,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  labelText: "E-posta",
                  prefixIcon: Icon(Icons.email),
                  filled: true,
                ),
                validator: (v) =>
                    !v!.contains("@") ? "Geçerli e-posta giriniz" : null,
              ),

              const SizedBox(height: 16),

              /// ŞİFRE
              TextFormField(
                controller: sifreCtrl,
                obscureText: true,
                decoration: const InputDecoration(
                  labelText: "Şifre",
                  prefixIcon: Icon(Icons.lock),
                  filled: true,
                ),
                validator: (v) =>
                    v!.length < 6 ? "En az 6 karakter" : null,
              ),

              const SizedBox(height: 24),

              /// CİNSİYET
              const Text(
                "Cinsiyet",
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              Row(
                children: [
                  Expanded(
                    child: RadioListTile(
                      title: const Text("Erkek"),
                      value: "Erkek",
                      groupValue: cinsiyet,
                      onChanged: (v) {
                        setState(() => cinsiyet = v!);
                      },
                    ),
                  ),
                  Expanded(
                    child: RadioListTile(
                      title: const Text("Kadın"),
                      value: "Kadın",
                      groupValue: cinsiyet,
                      onChanged: (v) {
                        setState(() => cinsiyet = v!);
                      },
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 16),

              /// ŞEHİR
              DropdownButtonFormField<String>(
                value: sehir,
                decoration: const InputDecoration(
                  labelText: "Şehir",
                  prefixIcon: Icon(Icons.location_on),
                  filled: true,
                ),
                items: ["Ankara", "İstanbul", "İzmir", "Bursa"]
                    .map((e) => DropdownMenuItem(
                          value: e,
                          child: Text(e),
                        ))
                    .toList(),
                onChanged: (v) => setState(() => sehir = v!),
              ),

              const SizedBox(height: 24),

              /// YAŞ
              Text("Yaş: ${yas.toInt()}"),
              Slider(
                value: yas,
                min: 10,
                max: 80,
                divisions: 70,
                label: yas.toInt().toString(),
                onChanged: (v) => setState(() => yas = v),
              ),

              /// SÖZLEŞME
              CheckboxListTile(
                value: sozlesme,
                title: const Text("Kullanım şartlarını kabul ediyorum"),
                onChanged: (v) => setState(() => sozlesme = v!),
              ),

              const SizedBox(height: 20),

              /// BUTON
              FilledButton(
                onPressed: () {
                  if (_formKey.currentState!.validate() && sozlesme) {
                    showDialog(
                      context: context,
                      builder: (_) => const AlertDialog(
                        title: Text("Başarılı"),
                        content: Text("Kayıt işlemi tamamlandı 🎉"),
                      ),
                    );
                  }
                },
                child: const Text("KAYIT OL"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

🧠 Material 3’e Özgü Kullanılan Bileşenler

ÖzellikAçıklama
useMaterial3: trueYeni tasarım sistemi
FilledButtonM3 buton
filled: trueModern input stili
ColorScheme.fromSeedDinamik renk
prefixIconUX iyileştirme

🎓 LAB / ÖDEV ÖNERİLERİ

✔ Tema rengini değiştir (seedColor)
✔ Dark Mode ekle
✔ Şifre görünür/gizli ikon yap
✔ Formu Card içine al
✔ Firebase ile kayıt yap


Material 3 formumuzu Clean Code prensiplerine uygun şekilde parçalara ayıralım.
Amaç: okunabilirlik, yeniden kullanılabilirlik ve bakım kolaylığı.


🧩 Flutter Formu Widget’lara Bölme

(Clean Code + Material 3)


🎯 Clean Code Neden Önemli?

❌ Tek dosyada 300–400 satır
✅ Küçük, anlamlı, tekrar kullanılabilir widget’lar

Avantajlar:

  • Kod daha anlaşılır

  • Hata ayıklama kolay

  • Widget’lar başka projelerde kullanılabilir

  • Profesyonel Flutter mimarisi


📁 Önerilen Proje Yapısı

lib/
 ├── main.dart
 ├── pages/
 │    └── register_page.dart
 └── widgets/
      ├── custom_text_field.dart
      ├── gender_selector.dart
      ├── city_dropdown.dart
      ├── age_slider.dart
      └── agreement_checkbox.dart

1️⃣ main.dart (Sade ve Temiz)

import 'package:flutter/material.dart';
import 'pages/register_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
      ),
      home: const RegisterPage(),
    );
  }
}

2️⃣ pages/register_page.dart (Ana Sayfa)

import 'package:flutter/material.dart';
import '../widgets/custom_text_field.dart';
import '../widgets/gender_selector.dart';
import '../widgets/city_dropdown.dart';
import '../widgets/age_slider.dart';
import '../widgets/agreement_checkbox.dart';

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

  @override
  State<RegisterPage> createState() => _RegisterPageState();
}

class _RegisterPageState extends State<RegisterPage> {
  final _formKey = GlobalKey<FormState>();

  final adCtrl = TextEditingController();
  final emailCtrl = TextEditingController();
  final sifreCtrl = TextEditingController();

  String cinsiyet = "Erkek";
  String sehir = "Ankara";
  double yas = 18;
  bool sozlesme = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Kayıt Ol")),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: ListView(
            children: [

              CustomTextField(
                controller: adCtrl,
                label: "Ad Soyad",
                icon: Icons.person,
                validator: (v) =>
                    v!.isEmpty ? "Bu alan boş bırakılamaz" : null,
              ),

              const SizedBox(height: 16),

              CustomTextField(
                controller: emailCtrl,
                label: "E-posta",
                icon: Icons.email,
                keyboardType: TextInputType.emailAddress,
                validator: (v) =>
                    !v!.contains("@") ? "Geçerli e-posta giriniz" : null,
              ),

              const SizedBox(height: 16),

              CustomTextField(
                controller: sifreCtrl,
                label: "Şifre",
                icon: Icons.lock,
                obscure: true,
                validator: (v) =>
                    v!.length < 6 ? "En az 6 karakter" : null,
              ),

              const SizedBox(height: 24),

              GenderSelector(
                value: cinsiyet,
                onChanged: (v) => setState(() => cinsiyet = v),
              ),

              const SizedBox(height: 16),

              CityDropdown(
                value: sehir,
                onChanged: (v) => setState(() => sehir = v),
              ),

              const SizedBox(height: 24),

              AgeSlider(
                value: yas,
                onChanged: (v) => setState(() => yas = v),
              ),

              AgreementCheckbox(
                value: sozlesme,
                onChanged: (v) => setState(() => sozlesme = v),
              ),

              const SizedBox(height: 20),

              FilledButton(
                onPressed: () {
                  if (_formKey.currentState!.validate() && sozlesme) {
                    showDialog(
                      context: context,
                      builder: (_) => const AlertDialog(
                        title: Text("Başarılı"),
                        content: Text("Kayıt tamamlandı 🎉"),
                      ),
                    );
                  }
                },
                child: const Text("KAYIT OL"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

3️⃣ widgets/custom_text_field.dart

import 'package:flutter/material.dart';

class CustomTextField extends StatelessWidget {
  final TextEditingController controller;
  final String label;
  final IconData icon;
  final bool obscure;
  final TextInputType keyboardType;
  final String? Function(String?)? validator;

  const CustomTextField({
    super.key,
    required this.controller,
    required this.label,
    required this.icon,
    this.obscure = false,
    this.keyboardType = TextInputType.text,
    this.validator,
  });

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      obscureText: obscure,
      keyboardType: keyboardType,
      validator: validator,
      decoration: InputDecoration(
        labelText: label,
        prefixIcon: Icon(icon),
        filled: true,
      ),
    );
  }
}

4️⃣ widgets/gender_selector.dart

import 'package:flutter/material.dart';

class GenderSelector extends StatelessWidget {
  final String value;
  final Function(String) onChanged;

  const GenderSelector({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text("Cinsiyet", style: TextStyle(fontWeight: FontWeight.bold)),
        RadioListTile(
          title: const Text("Erkek"),
          value: "Erkek",
          groupValue: value,
          onChanged: (v) => onChanged(v!),
        ),
        RadioListTile(
          title: const Text("Kadın"),
          value: "Kadın",
          groupValue: value,
          onChanged: (v) => onChanged(v!),
        ),
      ],
    );
  }
}

5️⃣ widgets/city_dropdown.dart

import 'package:flutter/material.dart';

class CityDropdown extends StatelessWidget {
  final String value;
  final Function(String) onChanged;

  const CityDropdown({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return DropdownButtonFormField<String>(
      value: value,
      decoration: const InputDecoration(
        labelText: "Şehir",
        prefixIcon: Icon(Icons.location_on),
        filled: true,
      ),
      items: ["Ankara", "İstanbul", "İzmir", "Bursa"]
          .map((e) => DropdownMenuItem(value: e, child: Text(e)))
          .toList(),
      onChanged: (v) => onChanged(v!),
    );
  }
}

6️⃣ widgets/age_slider.dart

import 'package:flutter/material.dart';

class AgeSlider extends StatelessWidget {
  final double value;
  final Function(double) onChanged;

  const AgeSlider({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text("Yaş: ${value.toInt()}"),
        Slider(
          value: value,
          min: 10,
          max: 80,
          divisions: 70,
          label: value.toInt().toString(),
          onChanged: onChanged,
        ),
      ],
    );
  }
}

7️⃣ widgets/agreement_checkbox.dart

import 'package:flutter/material.dart';

class AgreementCheckbox extends StatelessWidget {
  final bool value;
  final Function(bool) onChanged;

  const AgreementCheckbox({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return CheckboxListTile(
      value: value,
      title: const Text("Kullanım şartlarını kabul ediyorum"),
      onChanged: (v) => onChanged(v!),
    );
  }
}

🧠 Clean Code Kazanımları

✔ Tek sorumluluk prensibi
✔ Yeniden kullanılabilir widget
✔ Okunabilirlik
✔ Kolay test & bakım
✔ Profesyonel Flutter mimarisi


Harika 👍
Şimdi Material 3 (Material You) + Dark Mode yapısını doğru, modern ve profesyonel şekilde ekleyelim.
Bu anlatım Clean Code, gerçek projede kullanılabilir ve ders/lab için birebirdir.


🌙 Flutter Material 3 – Dark Mode Rehberi

(Modern, Sistem Uyumlu & Manuel Kontrol)


🎯 Dark Mode Türleri (ÖNEMLİ)

Flutter’da 3 farklı Dark Mode yaklaşımı vardır:

YöntemAçıklama
ThemeMode.system📱 Cihaz ayarına göre
ThemeMode.light☀️ Zorla açık tema
ThemeMode.dark🌙 Zorla koyu tema
Manuel Switch⚙️ Kullanıcı seçer

Biz burada EN PROFESYONEL YOLU yapacağız:
👉 Material 3 + Sistem uyumlu + Switch ile manuel kontrol


📁 Güncel Proje Yapısı

lib/
 ├── main.dart
 ├── pages/
 │    └── register_page.dart
 └── widgets/
      ├── theme_switch.dart
      ├── custom_text_field.dart
      └── ...

1️⃣ main.dart

(Material 3 + Light & Dark Tema Tanımı)

import 'package:flutter/material.dart';
import 'pages/register_page.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ThemeMode _themeMode = ThemeMode.system;

  void toggleTheme(bool isDark) {
    setState(() {
      _themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,

      /// ☀️ LIGHT THEME
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.light,
        ),
      ),

      /// 🌙 DARK THEME
      darkTheme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.dark,
        ),
      ),

      themeMode: _themeMode,
      home: RegisterPage(onThemeChanged: toggleTheme),
    );
  }
}

2️⃣ pages/register_page.dart

(Tema Switch’i Sayfaya Bağlama)

import 'package:flutter/material.dart';
import '../widgets/theme_switch.dart';

class RegisterPage extends StatelessWidget {
  final Function(bool) onThemeChanged;

  const RegisterPage({super.key, required this.onThemeChanged});

  @override
  Widget build(BuildContext context) {
    final isDark =
        Theme.of(context).brightness == Brightness.dark;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Kayıt Ol"),
        actions: [
          ThemeSwitch(
            value: isDark,
            onChanged: onThemeChanged,
          ),
        ],
      ),
      body: const Center(
        child: Text(
          "Material 3 Dark Mode Aktif 🌙",
          style: TextStyle(fontSize: 18),
        ),
      ),
    );
  }
}

3️⃣ widgets/theme_switch.dart

(Yeniden Kullanılabilir Dark Mode Switch)

import 'package:flutter/material.dart';

class ThemeSwitch extends StatelessWidget {
  final bool value;
  final Function(bool) onChanged;

  const ThemeSwitch({
    super.key,
    required this.value,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return Switch(
      value: value,
      onChanged: onChanged,
    );
  }
}

🎨 Material 3 Dark Mode’da Otomatik Olanlar

Material 3 sayesinde şunlar kendiliğinden uyum sağlar:

✔ TextField arka planları
✔ Icon renkleri
✔ Divider & border tonları
✔ FilledButton / Checkbox / Radio
✔ AlertDialog

👉 Ekstra renk tanımlamaya gerek yok


🧠 En İyi Uygulamalar (BEST PRACTICE)

ColorScheme.fromSeed() kullan
✅ Aynı seedColor ile light/dark üret
✅ Manuel Colors.black / Colors.white yazma
✅ Kontrastı Material’a bırak
❌ Sabit renk kullanma


🎓 LAB / ÖDEV FİKİRLERİ

1️⃣ Switch’i ayarlar sayfasına taşı
2️⃣ Tema seçimini SharedPreferences ile kaydet
3️⃣ Sistem mod + manuel mod seçimi ekle
4️⃣ Dark mode için özel ikon değiştir
5️⃣ Splash Screen tema uyumlu yap


Harika 👍
Şimdi Material 3 + Dark Mode yapısını PROFESYONEL SEVİYEDE yöneteceğiz.
Bu anlatımda iki yaklaşımı da göstereceğim:

1️⃣ Provider ile Theme Management
2️⃣ Riverpod ile Theme Management (önerilen, modern yol)

🔥 Gerçek projelerde kullanılan mimari budur.
“State yukarıda, UI aşağıda” mantığını tam öğretir.


🎨 Flutter Theme Management

Provider & Riverpod ile Dark Mode (Material 3)


🧠 Neden State Management?

setState() → küçük demo
✅ Provider / Riverpod → gerçek uygulama

Avantajlar:

  • Tema her sayfada otomatik değişir

  • Kod daha temiz

  • Test & bakım kolay

  • Global state kontrolü


🟦 YÖNTEM 1: PROVIDER


📦 1. Bağımlılık (pubspec.yaml)

dependencies:
  provider: ^6.1.2

📁 Proje Yapısı

lib/
 ├── main.dart
 ├── providers/
 │    └── theme_provider.dart
 └── pages/
      └── register_page.dart

2️⃣ providers/theme_provider.dart

import 'package:flutter/material.dart';

class ThemeProvider extends ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.system;

  ThemeMode get themeMode => _themeMode;

  bool get isDark => _themeMode == ThemeMode.dark;

  void toggleTheme(bool isDark) {
    _themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }
}

3️⃣ main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/theme_provider.dart';
import 'pages/register_page.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => ThemeProvider(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final themeProvider = context.watch<ThemeProvider>();

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: themeProvider.themeMode,

      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.light,
        ),
      ),

      darkTheme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.dark,
        ),
      ),

      home: const RegisterPage(),
    );
  }
}

4️⃣ pages/register_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/theme_provider.dart';

class RegisterPage extends StatelessWidget {
  const RegisterPage({super.key});

  @override
  Widget build(BuildContext context) {
    final themeProvider = context.watch<ThemeProvider>();

    return Scaffold(
      appBar: AppBar(
        title: const Text("Kayıt Ol"),
        actions: [
          Switch(
            value: themeProvider.isDark,
            onChanged: themeProvider.toggleTheme,
          ),
        ],
      ),
      body: const Center(
        child: Text("Provider ile Dark Mode 🌙"),
      ),
    );
  }
}

✅ Provider Özeti

✔ Öğrenmesi kolay
✔ Küçük–orta projeler
✔ Eğitim için ideal
❌ Büyük projelerde karmaşıklaşabilir


🟪 YÖNTEM 2: RIVERPOD (ÖNERİLEN 🔥)

⚠️ Riverpod = Provider’ın modern, güvenli ve güçlü hali


📦 1. Bağımlılık

dependencies:
  flutter_riverpod: ^2.5.1

📁 Proje Yapısı

lib/
 ├── main.dart
 ├── providers/
 │    └── theme_provider.dart
 └── pages/
      └── register_page.dart

2️⃣ providers/theme_provider.dart

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

final themeProvider =
    StateNotifierProvider<ThemeNotifier, ThemeMode>(
  (ref) => ThemeNotifier(),
);

class ThemeNotifier extends StateNotifier<ThemeMode> {
  ThemeNotifier() : super(ThemeMode.system);

  void toggle(bool isDark) {
    state = isDark ? ThemeMode.dark : ThemeMode.light;
  }
}

3️⃣ main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/theme_provider.dart';
import 'pages/register_page.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(themeProvider);

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: themeMode,

      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.light,
        ),
      ),

      darkTheme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
          brightness: Brightness.dark,
        ),
      ),

      home: const RegisterPage(),
    );
  }
}

4️⃣ pages/register_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/theme_provider.dart';

class RegisterPage extends ConsumerWidget {
  const RegisterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(themeProvider);
    final isDark = themeMode == ThemeMode.dark;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Kayıt Ol"),
        actions: [
          Switch(
            value: isDark,
            onChanged: (v) =>
                ref.read(themeProvider.notifier).toggle(v),
          ),
        ],
      ),
      body: const Center(
        child: Text("Riverpod ile Dark Mode 🌙"),
      ),
    );
  }
}

🧠 Provider vs Riverpod

ÖzellikProviderRiverpod
Global state⚠️
Compile-time safety
Test edilebilirlikOrta⭐⭐⭐
Büyük projeler
Önerilen

🏆 SONUÇ (ÖĞRETMEN / PROJE BAKIŞI)

  • 🟩 Eğitim / küçük projeler → Provider

  • 🟪 Gerçek uygulama / ileri seviye → Riverpod

Flutter ekosisteminde 2025+ standart: Riverpod



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