Flutter'da Radio ve RadioListTile Kullanımı

Flutter'da formlar ve kullanıcı seçimleri oluştururken Radio butonları en temel yapı taşlarından biridir.

Kullanıcıya "birçok seçenek arasından sadece birini seçtirmek" istediğinizde bu widget'ları kullanırsınız. İki temel widget mevcuttur: Radio (sadece yuvarlak buton) ve RadioListTile (metin ve tıklanabilir alan içeren liste elemanı).

Flutter’da kullanıcıdan tek bir seçenek seçmesini istediğimiz durumlarda en doğru bileşenlerden biri Radio ve RadioListTile’dır. Özellikle anketler, ayar ekranları, cinsiyet/seviye/tema seçimleri gibi senaryolarda sıkça kullanılır.


Radio butonlarının çalışma mantığı oldukça basittir ancak State Management (Durum Yönetimi) ile doğru kurgulanması gerekir. Mantık şudur:

  1. Value (Değer): O butonun temsil ettiği kimlik (Örn: "Elma").

  2. GroupValue (Grup Değeri): O an seçili olan değer (Örn: Şu an "Armut" seçili).

  3. Mantık: Eğer value == groupValue ise buton "seçili" (dolu) görünür.

1. Radio Widget'ı

Radio, en yalın halidir. Yanında bir metin barındırmaz, sadece yuvarlak seçim kutusunu oluşturur. Metin eklemek için genellikle bir Row içine alınır.

Temel Özellikleri:

  • value: Bu butonun taşıdığı değer.

  • groupValue: Şu an grupta seçili olan değer (State'den gelir).

  • onChanged: Tıklandığında çalışacak fonksiyon.

Örnek Kullanım:

Dart
int? _secilenDeger = 1; // Başlangıçta 1 numaralı seçenek seçili

// ... Widget ağacı içinde:
Row(
  children: [
    Radio<int>(
      value: 1, // Bu butonun değeri 1
      groupValue: _secilenDeger, // Eğer _secilenDeger 1 ise işaretli olur
      onChanged: (int? value) {
        setState(() {
          _secilenDeger = value;
        });
      },
    ),
    Text("Seçenek 1"),
  ],
)

Dikkat: Radio kullanırken kullanıcı sadece yuvarlak butona tıkladığında seçim gerçekleşir. Yanındaki "Seçenek 1" yazısına tıklarsa seçim değişmez. Bu durum kullanıcı deneyimi (UX) açısından her zaman ideal değildir.


2. RadioListTile Widget'ı (Önerilen)

RadioListTile, Material Design standartlarına uygun, satır bazlı bir yapı sunar. Hem butonu hem de metni içerir. En büyük avantajı, kullanıcı metne tıkladığında da seçimin gerçekleşmesidir.

Temel Özellikleri:

  • title: Ana metin.

  • subtitle: Alt açıklama metni.

  • secondary: Genellikle sola veya sağa eklenen ikon.

  • contentPadding: Kenar boşlukları.

  • activeColor: Seçildiğinde alacağı renk.

Örnek Kullanım:

Dart
RadioListTile<String>(
  title: const Text("Kredi Kartı"),
  subtitle: const Text("Taksit seçenekleri mevcut"),
  value: "kredi_karti",
  groupValue: _odemeYontemi,
  onChanged: (String? value) {
    setState(() {
      _odemeYontemi = value;
    });
  },
  secondary: const Icon(Icons.credit_card),
)

3. Tam Kapsamlı Örnek Proje

Aşağıdaki kodu kopyalayıp boş bir main.dart dosyasına yapıştırarak çalıştırabilirsiniz. Bu örnekte "Cinsiyet Seçimi" (Enum yapısı ile) ve "Tema Rengi Seçimi" senaryolarını göreceksiniz.

Best Practice olarak seçenekleri yönetmek için Enum kullanacağız.

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

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

// Seçenekleri tip güvenliği (Type Safety) için Enum ile tanımlamak en iyisidir.
enum Cinsiyet { erkek, kadin, belirtmekIstemiyorum }

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

  @override
  State<RadioOrnekSayfasi> createState() => _RadioOrnekSayfasiState();
}

class _RadioOrnekSayfasiState extends State<RadioOrnekSayfasi> {
  // Başlangıç değeri
  Cinsiyet? _secilenCinsiyet = Cinsiyet.erkek;
  
  // Dinamik liste için örnek
  String _favoriMeyve = "Elma";
  final List<String> _meyveler = ["Elma", "Armut", "Muz", "Çilek"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Radio & RadioListTile Rehberi'),
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              "1. Temel Radio Kullanımı (Enum ile):",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            
            // Yöntem 1: Basit Radio ve Row
            Row(
              children: [
                Radio<Cinsiyet>(
                  value: Cinsiyet.erkek,
                  groupValue: _secilenCinsiyet,
                  activeColor: Colors.blue, // Seçili renk
                  onChanged: (Cinsiyet? value) {
                    setState(() {
                      _secilenCinsiyet = value;
                    });
                  },
                ),
                const Text("Erkek"),
                
                Radio<Cinsiyet>(
                  value: Cinsiyet.kadin,
                  groupValue: _secilenCinsiyet,
                  activeColor: Colors.pink,
                  onChanged: (Cinsiyet? value) {
                    setState(() {
                      _secilenCinsiyet = value;
                    });
                  },
                ),
                const Text("Kadın"),
              ],
            ),

            const Divider(height: 40),

            const Text(
              "2. RadioListTile Kullanımı (Dinamik Liste):",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const Text("Hangi meyveyi seversin?", style: TextStyle(color: Colors.grey)),
            
            // Yöntem 2: RadioListTile
            // Liste elemanlarını döngü ile oluşturuyoruz
            ..._meyveler.map((meyve) {
              return RadioListTile<String>(
                title: Text(meyve),
                value: meyve,
                groupValue: _favoriMeyve,
                activeColor: Colors.deepPurple,
                contentPadding: EdgeInsets.zero, // Boşlukları sıfırlama
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                  side: BorderSide(color: Colors.grey.shade200)
                ),
                onChanged: (String? value) {
                  setState(() {
                    _favoriMeyve = value!;
                  });
                },
              );
            }),

            const SizedBox(height: 20),
            Container(
              padding: const EdgeInsets.all(12),
              color: Colors.yellow.shade100,
              child: Text("Seçilen Cinsiyet: ${_secilenCinsiyet?.name}\nSeçilen Meyve: $_favoriMeyve"),
            )
          ],
        ),
      ),
    );
  }
}

Radio ve RadioListTile Karşılaştırması

Hangi durumda hangisini kullanmalısınız?

ÖzellikRadioRadioListTile
GörünümSadece yuvarlak buton.Buton + Başlık + Alt Başlık + İkon.
Tıklama AlanıSadece butonun kendisi (küçük).Satırın tamamı (metin dahil).
YerleşimRow veya özel tasarım içinde esnek.Standart liste görünümü.
Kullanım YeriYan yana (horizontal) seçenekler için.Alt alta (vertical) uzun listeler için.
ÖzelleştirmeTasarımı tamamen size bırakır.Material Design kurallarına bağlıdır.

İpuçları ve Püf Noktaları

  1. Tip Güvenliği (Generics): Widget'ları kullanırken her zaman tipi belirtin. Örn: Radio<int> veya RadioListTile<String>. Bu, yanlış türde veri atamanızı engeller.

  2. Renk Değiştirme: activeColor parametresi ile seçili olduğunda alacağı rengi değiştirebilirsiniz. fillColor ile daha detaylı (seçili değilkenki renk vb.) ayarlar yapılabilir.

  3. TileColor: RadioListTile içinde tileColor ve selectedTileColor kullanarak tüm satırın arka plan rengini değiştirebilirsiniz.

Bu yapıyı kullanarak uygulamanızda ayarlar menüleri, filtreleme seçenekleri veya anket formları oluşturabilirsiniz.


1️⃣ Radio Nedir?

Radio, birden fazla seçenek içinden yalnızca bir tanesinin seçilebildiği input widget’ıdır.

📌 Checkbox’tan farkı:

  • Checkbox → Çoklu seçim

  • Radio → Tekli seçim


2️⃣ Radio Temel Kullanımı

Radio çalışabilmesi için 3 temel parametre gerekir:

Radio<T>(
  value: T,
  groupValue: T?,
  onChanged: (T?) {},
)

🔎 Parametre Açıklaması

ParametreAçıklama
valueBu radio’nun değeri
groupValueSeçili olan değer
onChangedSeçim değiştiğinde çalışır

🧠 En Önemli Mantık: groupValue

Radio’ların bağlı olduğu ortak değişkendir.

Eğer:

value == groupValue

ise o radio seçili görünür.


✅ Basit Örnek

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

  @override
  State<GenderSelector> createState() => _GenderSelectorState();
}

class _GenderSelectorState extends State<GenderSelector> {
  String? selectedGender;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            Radio<String>(
              value: "Erkek",
              groupValue: selectedGender,
              onChanged: (value) {
                setState(() {
                  selectedGender = value;
                });
              },
            ),
            const Text("Erkek"),
          ],
        ),
        Row(
          children: [
            Radio<String>(
              value: "Kadın",
              groupValue: selectedGender,
              onChanged: (value) {
                setState(() {
                  selectedGender = value;
                });
              },
            ),
            const Text("Kadın"),
          ],
        ),
      ],
    );
  }
}

3️⃣ RadioListTile Nedir?

RadioListTile, Radio + Text + Padding + InkWell birleşimidir.

Yani tıklanabilir alanı büyütür ve daha profesyonel görünüm sağlar.


✅ RadioListTile Kullanımı

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

  @override
  State<LevelSelector> createState() => _LevelSelectorState();
}

class _LevelSelectorState extends State<LevelSelector> {
  String? selectedLevel;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        RadioListTile<String>(
          title: const Text("Başlangıç"),
          value: "Beginner",
          groupValue: selectedLevel,
          onChanged: (value) {
            setState(() {
              selectedLevel = value;
            });
          },
        ),
        RadioListTile<String>(
          title: const Text("Orta"),
          value: "Intermediate",
          groupValue: selectedLevel,
          onChanged: (value) {
            setState(() {
              selectedLevel = value;
            });
          },
        ),
        RadioListTile<String>(
          title: const Text("İleri"),
          value: "Advanced",
          groupValue: selectedLevel,
          onChanged: (value) {
            setState(() {
              selectedLevel = value;
            });
          },
        ),
      ],
    );
  }
}

4️⃣ Radio vs RadioListTile Karşılaştırma

ÖzellikRadioRadioListTile
Sadece buton
Metin otomatik
Tıklama alanı geniş
Profesyonel görünümOrtaYüksek
Form ekranlarıManuel düzenHazır yapı

👉 Gerçek projelerde çoğunlukla RadioListTile tercih edilir.


5️⃣ Enum ile Profesyonel Kullanım (Clean Code)

String yerine enum kullanmak daha güvenlidir.

enum ThemeModeOption { light, dark, system }

Enum ile RadioListTile

ThemeModeOption? selectedTheme;

Column(
  children: [
    RadioListTile<ThemeModeOption>(
      title: const Text("Light Mode"),
      value: ThemeModeOption.light,
      groupValue: selectedTheme,
      onChanged: (value) {
        setState(() {
          selectedTheme = value;
        });
      },
    ),
    RadioListTile<ThemeModeOption>(
      title: const Text("Dark Mode"),
      value: ThemeModeOption.dark,
      groupValue: selectedTheme,
      onChanged: (value) {
        setState(() {
          selectedTheme = value;
        });
      },
    ),
  ],
)

✅ Tip güvenliği
✅ Daha temiz kod
✅ Büyük projelerde hata riskini azaltır


6️⃣ Material 3 Uyumlu Tasarım

Material 3 aktif etmek için:

MaterialApp(
  theme: ThemeData(
    useMaterial3: true,
  ),
)

🎨 Stil Özelleştirme

RadioListTile<String>(
  activeColor: Colors.deepPurple,
  tileColor: Colors.grey.shade100,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  title: const Text("Seçenek"),
  value: "A",
  groupValue: selectedValue,
  onChanged: (value) {},
)

7️⃣ Form İçinde Radio Kullanımı

Radio’yu Form ile kullanırken validation manuel yapılır.

if (selectedValue == null) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text("Lütfen bir seçim yapın")),
  );
}

8️⃣ Mini Proje: Ayar Ekranı (Material 3)

Özellikler:

  • Tema seçimi (RadioListTile)

  • Bildirim tercihi

  • Kaydet butonu

  • Snackbar feedback

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

  @override
  State<SettingsScreen> createState() => _SettingsScreenState();
}

enum ThemeOption { light, dark }

class _SettingsScreenState extends State<SettingsScreen> {
  ThemeOption? selectedTheme = ThemeOption.light;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Ayarlar")),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            const Text(
              "Tema Seçimi",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            RadioListTile<ThemeOption>(
              title: const Text("Açık Tema"),
              value: ThemeOption.light,
              groupValue: selectedTheme,
              onChanged: (value) {
                setState(() {
                  selectedTheme = value;
                });
              },
            ),
            RadioListTile<ThemeOption>(
              title: const Text("Koyu Tema"),
              value: ThemeOption.dark,
              groupValue: selectedTheme,
              onChanged: (value) {
                setState(() {
                  selectedTheme = value;
                });
              },
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text("Ayarlar kaydedildi")),
                );
              },
              child: const Text("Kaydet"),
            )
          ],
        ),
      ),
    );
  }
}

9️⃣ Sık Yapılan Hatalar

❌ groupValue null bırakmak
❌ setState çağırmamak
❌ String yerine enum kullanmamak
❌ Çok sayıda Radio’yu Column içinde scroll olmadan kullanmak


🔟 Performans İpuçları

  • Büyük listelerde ListView.builder kullanın

  • Enum tercih edin

  • Sabit metinlerde const kullanın

  • Gereksiz rebuild’lerden kaçının


📌 Özet

✔ Radio → Tekli seçim
✔ groupValue → Kontrol mekanizması
✔ RadioListTile → Daha profesyonel kullanım
✔ Enum → Temiz ve güvenli kod
✔ Material 3 → Modern tasarım


Normalde TextFormField'in kendi içinde bir validator özelliği vardır. Ancak Radio veya RadioListTile yalın haldeyken Form yapısından habersizdir. Onları bir forma dahil etmek ve "Burası boş geçilemez!" dedirtmek için FormField widget'ı ile sarmalamamız gerekir.

Aşağıda, bir kullanıcının eğitim durumunu seçmesini isteyen ve seçim yapmadan "Kaydet"e basarsa kırmızı hata mesajı gösteren tam çalışan kod yapısı bulunmaktadır.

Önemli Nokta: FormField Mantığı

Bu kodda göreceğiniz sihirli değnek FormField widget'ıdır. Bu widget, Radio grubunu sarmalar ve şunları sağlar:

  1. Validator: Seçim yapılıp yapılmadığını kontrol eder.

  2. Builder: Hata mesajını (kırmızı yazı) ekrana basmamızı sağlar.

  3. state.didChange: Seçim yapıldığında Form'a "Hey, değer değişti!" haberini verir.

Kod Örneği

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

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

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

  @override
  State<RadioFormPage> createState() => _RadioFormPageState();
}

class _RadioFormPageState extends State<RadioFormPage> {
  // Formun durumunu kontrol etmek için anahtar
  final _formKey = GlobalKey<FormState>();
  
  // Seçilen değeri tutacak değişken
  String? _egitimDurumu;

  // Seçeneklerimiz
  final List<String> _secenekler = [
    "İlköğretim",
    "Lise",
    "Üniversite",
    "Yüksek Lisans"
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Radio Form Validation")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey, // Form anahtarını buraya veriyoruz
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                "Eğitim Durumunuz:",
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 10),

              // --- İŞİN PÜF NOKTASI BURASI ---
              // Radio grubunu FormField içine alıyoruz
              FormField<String>(
                // 1. Kural (Validator): Eğer değer null ise hata döndür
                validator: (value) {
                  if (value == null) {
                    return "Lütfen bir eğitim durumu seçiniz!";
                  }
                  return null;
                },
                builder: (FormFieldState<String> state) {
                  return Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // Seçenekleri listeliyoruz
                      ..._secenekler.map((e) => RadioListTile<String>(
                        title: Text(e),
                        value: e,
                        // Hem yerel değişkeni hem de form state'ini kontrol edebiliriz
                        groupValue: _egitimDurumu, 
                        onChanged: (val) {
                          setState(() {
                            _egitimDurumu = val;
                          });
                          // BURASI ÇOK ÖNEMLİ:
                          // FormField'e değerin değiştiğini bildiriyoruz
                          // Böylece hata mesajı varsa silinir.
                          state.didChange(val); 
                        },
                      )),

                      // 2. Hata Mesajı Gösterimi
                      // Eğer formda hata varsa (state.hasError), kırmızı metni göster
                      if (state.hasError)
                        Padding(
                          padding: const EdgeInsets.only(left: 16.0, top: 5.0),
                          child: Text(
                            state.errorText!,
                            style: TextStyle(
                              color: Theme.of(context).colorScheme.error,
                              fontSize: 12,
                            ),
                          ),
                        ),
                    ],
                  );
                },
              ),
              // --------------------------------

              const SizedBox(height: 30),
              
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.blue,
                    foregroundColor: Colors.white,
                  ),
                  onPressed: () {
                    // Butona basıldığında doğrulama yap
                    if (_formKey.currentState!.validate()) {
                      // Hata yoksa burası çalışır
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text("Kaydedildi: $_egitimDurumu")),
                      );
                    } else {
                      // Hata varsa validator çalışır ve kırmızı yazılar çıkar
                      print("Form geçerli değil");
                    }
                  },
                  child: const Text("KAYDET"),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Kodun Çalışma Mantığı

  1. FormField Sarıcı: RadioListTile widget'larını doğrudan Column içine koymak yerine FormField'in builder parametresi içine koyduk. Bu sayede Radio grubu bir bütün olarak "tek bir form elemanı" gibi davranır.

  2. state.didChange(val): Kullanıcı bir şeye tıkladığında sadece setState yapıp ekranı güncellemek yetmez. state.didChange(val) kodunu da ekledik. Bu kod, FormField'e "Kullanıcı bir şey seçti, artık değer null değil, varsa hata mesajını kaldır" der.

  3. state.hasError: builder içinde en alta bir if bloğu ekledik. Eğer kullanıcı "Kaydet"e bastığında bir seçim yapmamışsa, validator devreye girer, state.hasError true olur ve kırmızı hata metni (state.errorText) görünür hale gelir.

Bu yapı sayesinde, karmaşık formlarda bile Radio butonlarını TextFormField kadar kolay bir şekilde yönetebilir ve doğrulayabilirsiniz.


🌙 Flutter Dark Mode + Light Mode Varyasyonu

RadioListTile ile Dinamik Tema Değişimi (Material 3)

Bu örnekte:

  • ✅ Light / Dark / System tema seçimi

  • ✅ RadioListTile ile seçim

  • ✅ Anında uygulanan tema değişimi

  • ✅ Clean mimari yapıya uygun çözüm

  • ✅ Material 3 tasarım

yapacağız.


🏗 Mimari Yapı

lib/
│
├── domain/
│   └── theme_option.dart
│
├── presentation/
│   ├── theme_notifier.dart
│   ├── widgets/
│   │   └── custom_radio_tile.dart
│   └── screens/
│       └── settings_screen.dart
│
└── main.dart

1️⃣ Domain Katmanı

📁 theme_option.dart

enum AppThemeOption {
  light,
  dark,
  system,
}

2️⃣ Theme State Yönetimi

Burada basit ve profesyonel bir çözüm olarak ChangeNotifier kullanıyoruz.

📁 theme_notifier.dart

import 'package:flutter/material.dart';
import '../domain/theme_option.dart';

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

  ThemeMode get themeMode => _themeMode;

  void updateTheme(AppThemeOption option) {
    switch (option) {
      case AppThemeOption.light:
        _themeMode = ThemeMode.light;
        break;
      case AppThemeOption.dark:
        _themeMode = ThemeMode.dark;
        break;
      case AppThemeOption.system:
        _themeMode = ThemeMode.system;
        break;
    }
    notifyListeners();
  }
}

3️⃣ Reusable Radio Widget

📁 custom_radio_tile.dart

import 'package:flutter/material.dart';

class CustomRadioTile<T> extends StatelessWidget {
  final String title;
  final T value;
  final T? groupValue;
  final ValueChanged<T?> onChanged;

  const CustomRadioTile({
    super.key,
    required this.title,
    required this.value,
    required this.groupValue,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return RadioListTile<T>(
      title: Text(title),
      value: value,
      groupValue: groupValue,
      onChanged: onChanged,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
    );
  }
}

4️⃣ Settings Screen

📁 settings_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../domain/theme_option.dart';
import '../theme_notifier.dart';
import '../widgets/custom_radio_tile.dart';

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

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

    AppThemeOption selectedOption;

    switch (themeNotifier.themeMode) {
      case ThemeMode.light:
        selectedOption = AppThemeOption.light;
        break;
      case ThemeMode.dark:
        selectedOption = AppThemeOption.dark;
        break;
      default:
        selectedOption = AppThemeOption.system;
    }

    return Scaffold(
      appBar: AppBar(title: const Text("Tema Ayarları")),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            const Text(
              "Tema Seçimi",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),

            CustomRadioTile<AppThemeOption>(
              title: "Açık Tema",
              value: AppThemeOption.light,
              groupValue: selectedOption,
              onChanged: (value) {
                if (value != null) {
                  themeNotifier.updateTheme(value);
                }
              },
            ),

            CustomRadioTile<AppThemeOption>(
              title: "Koyu Tema",
              value: AppThemeOption.dark,
              groupValue: selectedOption,
              onChanged: (value) {
                if (value != null) {
                  themeNotifier.updateTheme(value);
                }
              },
            ),

            CustomRadioTile<AppThemeOption>(
              title: "Sistem Varsayılanı",
              value: AppThemeOption.system,
              groupValue: selectedOption,
              onChanged: (value) {
                if (value != null) {
                  themeNotifier.updateTheme(value);
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

5️⃣ main.dart (Material 3 + Dynamic Theme)

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'presentation/theme_notifier.dart';
import 'presentation/screens/settings_screen.dart';

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

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

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

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: themeNotifier.themeMode,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.deepPurple,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.deepPurple,
        brightness: Brightness.dark,
      ),
      home: const SettingsScreen(),
    );
  }
}

🧠 Tema Akış Diyagramı

Kullanıcı Radio Seçer
        │
        ▼
ThemeNotifier.updateTheme()
        │
        ▼
ThemeMode değişir
        │
        ▼
notifyListeners()
        │
        ▼
MaterialApp rebuild olur
        │
        ▼
UI Light / Dark olarak güncellenir

🎨 Light vs Dark Görsel Davranış

ÖzellikLightDark
Arka PlanBeyazKoyu gri
TextSiyahBeyaz
SurfaceAçık tonKoyu ton
ShadowHafifDaha belirgin

Material 3 otomatik uyum sağlar.


🚀 Production Seviyesine Taşımak İçin

Bir üst seviyede:

  • SharedPreferences ile kalıcı tema kaydı

  • Splash screen’de otomatik yükleme

  • Riverpod ile state yönetimi

  • Animated theme geçişi

  • System theme değişimini dinleme

eklenebilir.

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