Flutter'da ThemeData ve Tema Yönetimi Rehberi

Flutter'da ThemeData, uygulamanızın görsel kimliğini (renkler, fontlar, buton şekilleri vb.) tek bir yerden yönetmenizi sağlayan en güçlü araçlardan biridir. Özellikle uygulamanız büyüdükçe her widget'a tek tek stil vermek yerine merkezi bir tema kullanmak, bakım maliyetini düşürür ve tutarlılığı artırır.


Bir mobil uygulama geliştirirken en önemli unsurlardan biri, kullanıcıya sunduğunuz görsel deneyimin tutarlılığıdır. Her sayfada farklı tonlarda maviler veya farklı boyutlarda başlıklar kullanmak, uygulamanızın amatör görünmesine neden olabilir. İşte tam bu noktada Flutter’ın ThemeData sınıfı devreye giriyor.

Bu rehberde, Flutter uygulamanızda global bir tema yapısını nasıl kuracağınızı, Material 3 özelliklerini nasıl aktif edeceğinizi ve Karanlık Mod (Dark Mode) desteğini nasıl ekleyeceğinizi adım adım inceleyeceğiz.

1. Neden ThemeData Kullanmalıyız?

Flutter'da her widget (Düğme, Metin, AppBar vb.) varsayılan bir stile sahiptir. Ancak bu varsayılanlar genellikle uygulamanızın marka kimliğiyle uyuşmaz. ThemeData kullanarak şunları yapabilirsiniz:

  • Tek Merkezden Yönetim: Ana renginizi değiştirmek istediğinizde 50 farklı sayfayı değil, sadece tema dosyanızı güncellersiniz.

  • Karanlık/Aydınlık Mod Desteği: Kullanıcının telefon ayarlarına göre otomatik değişen tasarımlar sunabilirsiniz.

  • Kod Tekrarını Önleme: Her Text widget'ı için style: TextStyle(...) yazmak yerine, global stilleri kullanırsınız.

2. Temel Kurulum ve Material 3

Flutter 3.x ile birlikte Google'ın yeni tasarım dili Material 3 varsayılan standart haline gelmeye başladı. Temanızı oluştururken useMaterial3: true parametresini kullanmak, daha modern animasyonlar ve renk paletleri sağlar.

En basit haliyle main.dart dosyasında tema kullanımı şöyledir:

Dart:
MaterialApp(
  title: 'Flutter Tema Rehberi',
  theme: ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), // Otomatik palet oluşturur
  ),
  home: AnaSayfa(),
);

İpucu: ColorScheme.fromSeed metodu, verdiğiniz tek bir ana renkten (örneğin mavi) yola çıkarak uyumlu, ikincil ve arka plan renklerini sizin için otomatik olarak üretir.

3. ThemeData'nın Derinlikleri: Özelleştirme

Otomatik renkler harika olsa da, genellikle daha spesifik kontrollere ihtiyaç duyarız. İşte ThemeData içinde en sık kullanılan özelleştirme alanları:

A. Renk Yönetimi (ColorScheme)

Eski primaryColor veya accentColor özellikleri artık kullanımdan kalkıyor (deprecated). Bunun yerine tüm renkleri colorScheme altında topluyoruz.

Dart:
ThemeData(
  useMaterial3: true,
  colorScheme: ColorScheme(
    brightness: Brightness.light,
    primary: Color(0xFF6200EE),
    onPrimary: Colors.white, // Primary üzerindeki yazı/ikon rengi
    secondary: Color(0xFF03DAC6),
    onSecondary: Colors.black,
    error: Colors.red,
    onError: Colors.white,
    surface: Colors.white, // Kart ve sayfa zeminleri
    onSurface: Colors.black,
  ),
);

B. Tipografi (TextTheme)

Uygulamanızdaki başlık, gövde metni ve etiket stillerini burada tanımlarsınız. Google Fonts paketi ile harika uyum sağlar.

Dart:
import 'package:google_fonts/google_fonts.dart';

// ...
textTheme: TextTheme(
  displayLarge: GoogleFonts.roboto(fontSize: 32, fontWeight: FontWeight.bold),
  bodyLarge: GoogleFonts.openSans(fontSize: 16, color: Colors.black87),
  labelSmall: TextStyle(fontSize: 11, letterSpacing: 1.5),
),

C. Bileşen Temaları (Component Themes)

Genel renklerin dışında, sadece "Button" veya "AppBar" gibi belirli bileşenlerin stillerini global olarak değiştirmek isterseniz, bileşen temalarını kullanmalısınız.

Dart:
appBarTheme: AppBarTheme(
  backgroundColor: Colors.blueGrey[900],
  centerTitle: true,
  elevation: 0,
),
elevatedButtonTheme: ElevatedButtonThemeData(
  style: ElevatedButton.styleFrom(
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
  ),
),

4. Uygulama İçinde Temayı Kullanmak

Temayı main.dart içinde tanımladık. Peki, diğer sayfalarda bu renklere veya fontlara nasıl erişeceğiz? Cevap: Theme.of(context).

Renk kodlarını (örneğin Colors.red) doğrudan widget'ların içine yazmak yerine (hard-coding), tema üzerinden çağırmalısınız. Bu sayede Karanlık Mod'a geçtiğinizde renkler otomatik güncellenir.

Dart:
// Yanlış Kullanım ❌
Container(
  color: Colors.white, // Karanlık modda göz alır!
  child: Text("Merhaba", style: TextStyle(color: Colors.black)),
)

// Doğru Kullanım ✅
Container(
  color: Theme.of(context).colorScheme.surface, 
  child: Text(
    "Merhaba", 
    style: Theme.of(context).textTheme.bodyLarge?.copyWith(
      fontWeight: FontWeight.bold, // Sadece gerekeni ezdik
    ),
  ),
)

5. Karanlık Mod (Dark Mode) Kurulumu

Modern uygulamaların olmazsa olmazı Karanlık Mod'dur. Flutter'da bunu yapmak inanılmaz derecede basittir. MaterialApp widget'ına hem theme (aydınlık) hem de darkTheme (karanlık) parametrelerini vermeniz yeterlidir.

Dart
MaterialApp(
  themeMode: ThemeMode.system, // Sistem ayarına göre otomatik geçiş (light/dark)
  
  // Aydınlık Tema
  theme: ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, brightness: Brightness.light),
  ),
  
  // Karanlık Tema
  darkTheme: ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, brightness: Brightness.dark),
    // Karanlık modda zeminler otomatik olarak koyu gri/siyah olur
  ),
  
  home: AnaSayfa(),
);

Özet ve İpuçları

  1. Material 3'ü Kucaklayın: useMaterial3: true ile uygulamanızı geleceğe hazırlayın.

  2. Hard-Code Yapmayın: Colors.blue yerine Theme.of(context).colorScheme.primary kullanın.

  3. FromSeed Kullanın: Renk uyumu konusunda uzman değilseniz, fromSeed metodu hayat kurtarıcıdır.

  4. Parçalara Bölün: main.dart dosyanızın şişmesini istemiyorsanız, temanızı theme.dart gibi ayrı bir dosyada tanımlayıp import edin.



Uygulama Genelinde Tutarlı ve Profesyonel Tasarım


1️⃣ Theme Nedir? Neden Kullanılır?

Flutter’da Theme, uygulamanın renk, yazı tipi, buton, ikon, arka plan gibi görsel özelliklerini merkezi olarak yönetmemizi sağlar.

Theme Kullanmanın Avantajları

✅ Tek noktadan tasarım kontrolü
✅ Kod tekrarını azaltır
✅ Dark / Light Mode desteği
✅ Profesyonel UI standardı
✅ Büyük projelerde bakım kolaylığı


2️⃣ ThemeData Nedir?

ThemeData, Flutter’da uygulama temasını tanımlayan ana sınıftır.

ThemeData(
  primaryColor: Colors.blue,
  fontFamily: 'Roboto',
)

Bu yapı genellikle MaterialApp içinde kullanılır.


3️⃣ ThemeData Nerede Tanımlanır?

📌 MaterialApp → theme / darkTheme

MaterialApp(
  title: 'Theme Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: HomePage(),
);

4️⃣ En Temel ThemeData Özellikleri

🎨 primarySwatch

Uygulamanın ana renk paleti

primarySwatch: Colors.green,

🎨 primaryColor

Ana renk (AppBar, Button vb.)

primaryColor: Colors.teal,

🌈 scaffoldBackgroundColor

Sayfa arka plan rengi

scaffoldBackgroundColor: Colors.grey.shade100,

🖋️ fontFamily

Genel yazı tipi

fontFamily: 'Poppins',

📌 pubspec.yaml eklemeyi unutma.


5️⃣ TextTheme (Yazı Stilleri)

Tüm metin stillerini merkezi tanımlar.

textTheme: TextTheme(
  headlineLarge: TextStyle(
    fontSize: 28,
    fontWeight: FontWeight.bold,
  ),
  bodyMedium: TextStyle(
    fontSize: 16,
  ),
),

📌 Kullanım:

Text(
  "Merhaba",
  style: Theme.of(context).textTheme.headlineLarge,
)

6️⃣ AppBar Theme

appBarTheme: AppBarTheme(
  backgroundColor: Colors.indigo,
  foregroundColor: Colors.white,
  centerTitle: true,
),

Etkisi:

  • Tüm AppBar’lar otomatik aynı olur

  • Kod tekrarını önler


7️⃣ Button Theme (Yeni Sistem)

ElevatedButtonTheme

elevatedButtonTheme: ElevatedButtonThemeData(
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.orange,
    foregroundColor: Colors.white,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
),

📌 Artık ButtonTheme kullanılmıyor (deprecated).


8️⃣ IconTheme

iconTheme: IconThemeData(
  color: Colors.blueGrey,
  size: 28,
),

9️⃣ InputDecorationTheme (TextField)

inputDecorationTheme: InputDecorationTheme(
  border: OutlineInputBorder(),
  focusedBorder: OutlineInputBorder(
    borderSide: BorderSide(color: Colors.blue),
  ),
  labelStyle: TextStyle(color: Colors.blue),
),

📌 Login / Register ekranlarında çok kritik


🔟 Dark Theme (Karanlık Mod)

MaterialApp(
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
  themeMode: ThemeMode.system,
);

🎯 Otomatik sistem temasına uyar.


1️⃣1️⃣ ColorScheme (Modern Yaklaşım)

Flutter’ın önerdiği yeni tema sistemi

theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.deepPurple,
  ),
  useMaterial3: true,
),

📌 Material 3 uyumlu
📌 Daha tutarlı renk yönetimi


1️⃣2️⃣ Theme.of(context) Nasıl Çalışır?

BuildContext, widget’ın hangi theme altında olduğunu bilir.

Theme.of(context).primaryColor

📌 Context yukarı doğru widget ağacını tarar.


1️⃣3️⃣ Gerçek Hayat Örneği – Kurumsal Tema

ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.blue,
  ),
  textTheme: TextTheme(
    headlineLarge: TextStyle(fontWeight: FontWeight.bold),
  ),
  appBarTheme: AppBarTheme(
    centerTitle: true,
  ),
  useMaterial3: true,
);

1️⃣4️⃣ Sık Yapılan Hatalar ⚠️

❌ Her widget’a renk vermek
❌ Theme yerine inline style kullanmak
❌ BuildContext yanlış yerde kullanmak
❌ Dark theme tanımlayıp themeMode ayarlamamak


1️⃣5️⃣ LAB Senaryosu 🧪

🎯 Amaç:
Öğrenciler tek bir ThemeData ile uygulama tasarlayacak

Görevler:

  1. Ana renk belirle

  2. Yazı stilini ayarla

  3. AppBar ve Button temasını oluştur

  4. Dark mode ekle

📌 Kazanımlar:

  • UI standardı

  • Context bilinci

  • Material tasarım


Özet

ÖzellikAmaç
ThemeData    Merkezi tasarım
TextTheme    Yazı kontrolü
ColorScheme    Modern renk yönetimi
DarkTheme    Kullanıcı deneyimi
Theme.of    Context okuma




Bir uygulamanın en havalı özelliklerinden biri, kullanıcının ruh haline göre temayı değiştirebilmesidir. Bunu yapmanın en temiz ve ek paket gerektirmeyen (dependency-free) yolu, Flutter'ın kendi içinde gelen ValueNotifier yapısını kullanmaktır.

Bu yöntem, sınıfta öğrencilerinize anlatırken harici kütüphanelerle (Provider, Bloc vb.) kafalarını karıştırmadan State Management (Durum Yönetimi) mantığını da kavramalarını sağlayacaktır.

İşte adım adım Dinamik Tema Değiştirici kodları ve açıklaması:

Adım 1: Mantığı Kurmak

Uygulamanın tamamını sarmalayacak ve tema bilgisini tutacak basit bir yöneticiye ihtiyacımız var. Bunun için global bir değişken kullanacağız.

Adım 2: Kodun Tamamı (main.dart)

Bu kodu doğrudan yeni bir Flutter projesindeki main.dart dosyanıza yapıştırarak çalıştırabilirsiniz.

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

// 1. ADIM: TEMA YÖNETİCİSİ (Global Değişken)
// Bu değişken, seçili olan temayı (Aydınlık, Karanlık veya Sistem) tutar.
// ValueNotifier sayesinde bu değer değiştiğinde, dinleyen herkes haberdar olur.
final ValueNotifier<ThemeMode> temaYoneticisi = ValueNotifier(ThemeMode.system);

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

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

  @override
  Widget build(BuildContext context) {
    // 2. ADIM: DİNLEYİCİ (Listener)
    // ValueListenableBuilder, 'temaYoneticisi' her değiştiğinde
    // içindeki builder metodunu tekrar çalıştırır ve arayüzü günceller.
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: temaYoneticisi,
      builder: (context, aktifTemaModu, child) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Tema Değiştirici Örneği',
          
          // Mevcut durum (State) buraya gelir
          themeMode: aktifTemaModu, 
          
          // Aydınlık Tema Ayarları (Material 3)
          theme: ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(
              seedColor: Colors.teal, 
              brightness: Brightness.light
            ),
          ),
          
          // Karanlık Tema Ayarları
          darkTheme: ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(
              seedColor: Colors.teal, // Aynı renk, ama karanlık mod versiyonu
              brightness: Brightness.dark
            ),
          ),
          
          home: const AnaSayfa(),
        );
      },
    );
  }
}

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

  // 3. ADIM: DEĞİŞTİRME FONKSİYONU
  void temayiDegistir() {
    // Eğer şu anki mod aydınlıksa karanlığa, değilse aydınlığa çevir
    // (Basit bir toggle mantığı)
    if (temaYoneticisi.value == ThemeMode.light) {
      temaYoneticisi.value = ThemeMode.dark;
    } else {
      temaYoneticisi.value = ThemeMode.light;
    }
  }

  @override
  Widget build(BuildContext context) {
    // Şu anki temanın renklerine erişmek için
    final renkler = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Dinamik Tema"),
        backgroundColor: renkler.inversePrimary, // Temaya uygun dinamik renk
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // İkonu temaya göre değiştirme
            Icon(
              temaYoneticisi.value == ThemeMode.dark 
                  ? Icons.nightlight_round 
                  : Icons.wb_sunny,
              size: 100,
              color: renkler.primary,
            ),
            const SizedBox(height: 20),
            Text(
              "Şu anki Mod:",
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            Text(
              temaYoneticisi.value == ThemeMode.dark ? "KARANLIK 🌙" : "AYDINLIK ☀️",
              style: Theme.of(context).textTheme.displayMedium?.copyWith(
                fontWeight: FontWeight.bold,
                color: renkler.primary,
              ),
            ),
            const SizedBox(height: 40),
            FilledButton.icon(
              onPressed: temayiDegistir,
              icon: const Icon(Icons.palette),
              label: const Text("Temayı Değiştir"),
            ),
          ],
        ),
      ),
    );
  }
}

Eğitim İçin Önemli Notlar

Bu örneği öğrencilerinize veya okuyucularınıza sunarken şu noktalara değinebilirsiniz:

  1. ValueNotifier Nedir?: Basit verileri (burada ThemeMode) saklayan ve değiştiğinde haber veren Flutter'ın yerleşik bir sınıfıdır.

  2. ValueListenableBuilder: Bu widget, sadece ilgili veriyi dinler. Tüm uygulamayı yeniden çizmek yerine (setState gibi), sadece MaterialApp'in tema kısmını günceller. Performans dostudur.

  3. Dinamik Renkler: Scaffold veya Text içinde renkleri sabit (Colors.red) vermedik. Hep Theme.of(context).colorScheme... kullandık. Bu sayede tema değişince her şey otomatik uyum sağladı.


Bir Sonraki Adım: Kalıcılık (Persistence)

Öğrencileriniz muhtemelen şunu soracaktır: "Hocam uygulamayı kapatıp açınca tema eski haline dönüyor, bunu nasıl kaydederiz?"

Bu soru gelirse, onlara shared_preferences paketini tanıtarak veriyi telefonun hafızasına (Local Storage) kaydetmeyi göstermek harika bir devam dersi olur.

Harika! Öğrencilerinize veya okuyucularınıza "Veri Kalıcılığı" (Data Persistence) kavramını anlatmak için mükemmel bir fırsat. Bir ayarın telefon kapatılıp açıldığında hatırlanması, profesyonel bir uygulamanın en temel özelliğidir.

Bunun için Flutter dünyasının standardı olan shared_preferences paketini kullanacağız. Bu paket, küçük verileri (ayarlar, bayraklar, basit metinler) telefonun hafızasında anahtar-değer (key-value) şeklinde saklar.

İşte adım adım Kalıcı Tema Yönetimi rehberi:

Adım 1: Paketi Ekleyelim

Öncelikle projenizin terminaline şu komutu yazarak paketi ekleyin:

Bash:
flutter pub add shared_preferences

Adım 2: Mantığı Kuralım

Kodlarımız biraz değişecek. Artık uygulama açılır açılmaz (runApp çalışmadan önce) hafızayı kontrol etmemiz ve son kaydedilen temayı okumamız gerekiyor.

  1. main fonksiyonu async olacak: Çünkü diskten okuma işlemi zaman alır.

  2. WidgetsFlutterBinding: Flutter motorunu manuel başlatacağız.

  3. Kaydetme İşlemi: Kullanıcı butona her bastığında yeni tercihi hafızaya yazacağız.

Adım 3: Kodun Tamamı (main.dart)

Bu kod, önceki örneğin üzerine inşa edilmiştir ve shared_preferences entegre edilmiş halidir.

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

// TEMA YÖNETİCİSİ (Global Değişken)
// Başlangıçta varsayılan olarak Sistem temasını atıyoruz,
// ancak main() içinde bunu hafızadan okuyup güncelleyeceğiz.
final ValueNotifier<ThemeMode> temaYoneticisi = ValueNotifier(ThemeMode.system);

// ---------------------------------------------------------
// 1. YARDIMCI SINIF: Hafıza İşlemleri
// Bu sınıfı öğrencileriniz için ayrı bir dosya (services/tema_servisi.dart)
// olarak da düşünebilirsiniz. Temiz kod için işlemleri ayırıyoruz.
// ---------------------------------------------------------
class TemaHafizasi {
  static const String _temaKey = "tema_tercihi"; // Anahtar kelimemiz

  // Tercihi Kaydet (True: Karanlık, False: Aydınlık)
  static Future<void> kaydet(bool isDark) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setBool(_temaKey, isDark);
  }

  // Tercihi Oku
  // Eğer hiç kayıt yoksa (ilk açılış), null döner.
  static Future<bool?> oku() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.getBool(_temaKey);
  }
}

// ---------------------------------------------------------
// 2. ANA FONKSİYON (Değişiklikler Burada!)
// ---------------------------------------------------------
void main() async {
  // Asenkron işlemler yapacağımız için motoru manuel başlatıyoruz.
  WidgetsFlutterBinding.ensureInitialized();

  // Hafızadaki tercihi oku
  final bool? karanlikMi = await TemaHafizasi.oku();

  // Eğer kayıt varsa ona göre ayarla, yoksa Sistem temasını kullan
  if (karanlikMi != null) {
    temaYoneticisi.value = karanlikMi ? ThemeMode.dark : ThemeMode.light;
  }

  runApp(const BenimUygulamam());
}

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

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: temaYoneticisi,
      builder: (context, aktifTemaModu, child) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Kalıcı Tema Örneği',
          themeMode: aktifTemaModu,
          
          // Aydınlık Tema
          theme: ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(
              seedColor: Colors.indigo, 
              brightness: Brightness.light
            ),
          ),
          
          // Karanlık Tema
          darkTheme: ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(
              seedColor: Colors.indigo,
              brightness: Brightness.dark
            ),
          ),
          
          home: const AnaSayfa(),
        );
      },
    );
  }
}

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

  // 3. BUTON MANTIĞI (Hem Değiştir Hem Kaydet)
  void temayiDegistir() {
    if (temaYoneticisi.value == ThemeMode.light) {
      // 1. Uygulama durumunu güncelle
      temaYoneticisi.value = ThemeMode.dark;
      // 2. Hafızaya kaydet (Kalıcılık)
      TemaHafizasi.kaydet(true); 
    } else {
      temaYoneticisi.value = ThemeMode.light;
      TemaHafizasi.kaydet(false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final renkler = Theme.of(context).colorScheme;
    final isDark = temaYoneticisi.value == ThemeMode.dark;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Kalıcı Tema"),
        backgroundColor: renkler.surfaceContainerHighest,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 500),
              transitionBuilder: (Widget child, Animation<double> animation) {
                return ScaleTransition(scale: animation, child: child);
              },
              child: Icon(
                isDark ? Icons.dark_mode : Icons.light_mode,
                key: ValueKey<bool>(isDark), // Animasyon için gerekli
                size: 120,
                color: isDark ? Colors.amber : Colors.orange,
              ),
            ),
            const SizedBox(height: 30),
            Text(
              "Ayarlar Kaydediliyor...",
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 10),
            Text(
              isDark ? "Karanlık Mod Açık" : "Aydınlık Mod Açık",
              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 40),
            FilledButton.icon(
              onPressed: temayiDegistir,
              icon: const Icon(Icons.save_as),
              label: const Text("Değiştir ve Kaydet"),
              style: FilledButton.styleFrom(
                padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Öğrenciler İçin Kritik Noktalar (Ders Notu)

Bu örneği anlatırken şu kavramlara vurgu yapabilirsiniz:

  1. Neden async main?: Normalde void main() senkron çalışır. Ancak diskten veri okumak (I/O işlemi) zaman alan bir iştir. Uygulama açılırken "bekle, önce ayarları oku" demek için await kullanıyoruz.

  2. WidgetsFlutterBinding: Flutter'ın arayüzü çizmeye başlamadan önce C++ motoruyla (engine) iletişim kurmasını sağlar. SharedPreferences native (yerel) kod kullandığı için, motor hazır olmadan çağırırsak hata alırız. Bu satır motoru "hazır ol" moduna getirir.

  3. Null Kontrolü: TemaHafizasi.oku() fonksiyonu null dönebilir (kullanıcı uygulamayı ilk kez açıyorsa). Bu durumda varsayılan bir değer atamak (bizim örneğimizde Sistem teması) önemlidir.


Bir Sonraki Adım: Hiyerarşi ve Düzen

Uygulama büyüdükçe bu renkleri main.dart içinde tutmak zorlaşacaktır. Pardus ETAP projelerinizde veya derslerinizde öğrencilerinizle daha büyük bir projeye girişirken, renk paletini ve tema ayarlarını ayrı bir dosyada (örneğin theme/app_theme.dart) nasıl organize edeceğinizi göstermemi ister misiniz? Bu, temiz kod (Clean Code) prensipleri için harika bir pratik olur.

Harika bir karar! Öğrencilerinize "Spagetti Kod" (karışık ve düzensiz kod) yerine, sektör standartlarına uygun Modüler Mimari alışkanlığını kazandırmak için en doğru adım budur.

Özellikle Pardus ETAP projeleriniz gibi, birden fazla sayfadan oluşan ve bakımının kolay olması gereken projelerde, renk kodlarının main.dart içinde kaybolmaması gerekir.

İşte projenizi profesyonelleştirecek dosya yapısı ve kodlar:

1. Dosya Yapısı (Klasör Düzeni)

Önce lib klasörü altında theme (veya core/theme) adında bir klasör oluşturun. Bu, projenin "çekirdek" ayarlarının olduğu yerdir.

lib/
├── main.dart
├── theme/
│   └── app_theme.dart  <-- Yeni dosyamız bu
└── screens/
    └── home_screen.dart

2. Tema Dosyası (lib/theme/app_theme.dart)

Bu dosyada AppTheme adında bir sınıf oluşturacağız. Bu sınıfın içinde static (nesne üretmeden erişilebilen) değişkenler tanımlayarak, uygulamanın her yerinden AppTheme.lightTheme diyerek erişebileceğiz.

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

class AppTheme {
  // Bu sınıfın başlatılmasını (instance) engellemek için private constructor
  AppTheme._();

  // Renk Paleti (Sabitler)
  // Burayı bir "Renk Havuzu" gibi düşünebilirsiniz.
  // Pardus kurumsal renklerini veya okul renklerini burada tanımlayabilirsiniz.
  static const Color _lightPrimaryColor = Color(0xFF1E88E5); // Örnek: Pardus Mavisi
  static const Color _darkPrimaryColor = Color(0xFF90CAF9);
  
  static const Color _lightBackground = Color(0xFFF5F5F5);
  static const Color _darkBackground = Color(0xFF121212);

  // ------------------------------------------------------------------
  // AYDINLIK TEMA (LIGHT THEME)
  // ------------------------------------------------------------------
  static final ThemeData lightTheme = ThemeData(
    useMaterial3: true,
    brightness: Brightness.light,
    
    // Renk Şeması
    colorScheme: ColorScheme.fromSeed(
      seedColor: _lightPrimaryColor,
      brightness: Brightness.light,
      surface: _lightBackground,
    ),

    // AppBar Teması (Global)
    appBarTheme: const AppBarTheme(
      centerTitle: true,
      backgroundColor: _lightPrimaryColor,
      foregroundColor: Colors.white, // Başlık ve ikon rengi
      elevation: 0,
    ),

    // Buton Teması (Global)
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: _lightPrimaryColor,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      ),
    ),

    // Metin Teması (İsteğe bağlı özelleştirme)
    textTheme: const TextTheme(
      displayLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.black87),
      bodyLarge: TextStyle(fontSize: 16, color: Colors.black87),
    ),
  );

  // ------------------------------------------------------------------
  // KARANLIK TEMA (DARK THEME)
  // ------------------------------------------------------------------
  static final ThemeData darkTheme = ThemeData(
    useMaterial3: true,
    brightness: Brightness.dark,

    colorScheme: ColorScheme.fromSeed(
      seedColor: _darkPrimaryColor,
      brightness: Brightness.dark,
      surface: _darkBackground,
    ),

    appBarTheme: const AppBarTheme(
      centerTitle: true,
      backgroundColor: _darkBackground, // Karanlık modda genellikle zemin rengi tercih edilir
      foregroundColor: _darkPrimaryColor,
      elevation: 0,
    ),

    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: _darkPrimaryColor,
        foregroundColor: Colors.black, // Koyu buton üzerinde siyah yazı daha okunaklıdır
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      ),
    ),
    
    textTheme: const TextTheme(
      displayLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white),
      bodyLarge: TextStyle(fontSize: 16, color: Colors.white70),
    ),
  );
}

3. Ana Dosyaya Entegre Etmek (main.dart)

Artık main.dart dosyamız tertemiz olacak. Yüzlerce satırlık tema kodları yerine sadece referans vereceğiz.

Not: Aşağıdaki kodda, bir önceki adımda yazdığımız ThemeManager ve SharedPreferences yapısının var olduğunu varsayıyorum. Sadece MaterialApp kısmını gösteriyorum:

Dart:
// İlgili importu eklemeyi unutmayın:
import 'theme/app_theme.dart'; 

// ... (main fonksiyonu ve diğer kodlar aynı)

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

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: temaYoneticisi,
      builder: (context, aktifTemaModu, child) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Clean Code Tema',
          
          themeMode: aktifTemaModu,
          
          // ARTIK SADECE BU KADAR! 👇
          theme: AppTheme.lightTheme,
          darkTheme: AppTheme.darkTheme,
          
          home: const AnaSayfa(),
        );
      },
    );
  }
}

Öğrenciler İçin "Neden Böyle Yaptık?" Özeti

Bu yapıyı sınıfta anlatırken şu analojiyi kullanabilirsiniz:

  1. Düzen: "Odanızdaki her şeyi (kıyafetler, kitaplar, tabaklar) yere atmak yerine; kıyafetleri dolaba, kitapları rafa koyduk. main.dart bizim salonumuz, orayı temiz tutmalıyız."

  2. Bakım Kolaylığı: "Yarın okul müdürü gelip 'Uygulamanın mavisini biraz daha koyu yapın' derse, 50 farklı dosyayı açıp aramak yerine sadece app_theme.dart dosyasına gidip tek bir satırı değiştireceğiz."

  3. Takım Çalışması: "Bir arkadaşınız ana sayfayı kodlarken, diğer arkadaşınız tema dosyasında renkleri ayarlayabilir. Birbirinizin kodunu bozmazsınız."


Bir Sonraki Adım: Özel Fontlar

Görselliği tamamlayan en önemli unsur Tipografidir.

Pardus ETAP tahtalarında veya öğrenci projelerinde standart fontlar yerine, uygulamanıza özel (örneğin Google Fonts'tan) fontları nasıl ekleyeceğinizi ve app_theme.dart içine nasıl entegre edeceğinizi anlatma.

Tipografi (yazı tipi), bir uygulamanın sadece okunabilirliğini değil, aynı zamanda karakterini belirler. Pardus ETAP üzerinde çalışacak bir uygulama için "ciddi ve kurumsal" bir font mu, yoksa öğrenciler için "eğlenceli ve yuvarlak hatlı" bir font mu seçeceğiniz tüm tasarımı değiştirir.

Flutter'da bunu yapmanın en modern ve kolay yolu google_fonts paketini kullanmaktır. Bu paket sayesinde binlerce fontu saniyeler içinde projenize dahil edebilirsiniz.

İşte adım adım Google Fonts Entegrasyonu:

Adım 1: Paketi Ekleyelim

Terminali açın ve projenizin ana dizininde şu komutu çalıştırın:

Bash
flutter pub add google_fonts

Bu komut, pubspec.yaml dosyanıza gerekli satırı otomatik olarak ekleyecektir.

Adım 2: Tema Dosyasını Güncelleyelim (app_theme.dart)

Şimdi daha önce oluşturduğumuz lib/theme/app_theme.dart dosyasını açalım. Burada kritik bir nokta var: Her bir metin stili (başlık, gövde vb.) için tek tek font tanımlamak yerine, tüm temaya tek satırda font giydireceğiz.

Örnek olarak, modern ve okunaklı bir font olan **"Lato"**yu kullanalım.

Dart:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; // 1. İMPORT EKLE

class AppTheme {
  AppTheme._();

  static const Color _lightPrimaryColor = Color(0xFF1E88E5);
  static const Color _lightBackground = Color(0xFFF5F5F5);
  
  static const Color _darkPrimaryColor = Color(0xFF90CAF9);
  static const Color _darkBackground = Color(0xFF121212);

  // ------------------------------------------------------------------
  // AYDINLIK TEMA (GÜNCELLENDİ)
  // ------------------------------------------------------------------
  static final ThemeData lightTheme = ThemeData(
    useMaterial3: true,
    brightness: Brightness.light,
    
    colorScheme: ColorScheme.fromSeed(
      seedColor: _lightPrimaryColor,
      surface: _lightBackground,
      brightness: Brightness.light,
    ),

    // KRİTİK NOKTA BURASI 👇
    // GoogleFonts.latoTextTheme() metodu, varsayılan metin temasını alır,
    // tüm başlık ve gövde yazılarına "Lato" fontunu uygular
    // ve renklerini koruyarak geri verir.
    textTheme: GoogleFonts.latoTextTheme(
      ThemeData.light().textTheme, 
    ),

    appBarTheme: const AppBarTheme(
      backgroundColor: _lightPrimaryColor,
      foregroundColor: Colors.white,
      centerTitle: true,
    ),
  );

  // ------------------------------------------------------------------
  // KARANLIK TEMA (GÜNCELLENDİ)
  // ------------------------------------------------------------------
  static final ThemeData darkTheme = ThemeData(
    useMaterial3: true,
    brightness: Brightness.dark,

    colorScheme: ColorScheme.fromSeed(
      seedColor: _darkPrimaryColor,
      surface: _darkBackground,
      brightness: Brightness.dark,
    ),

    // Karanlık mod için de aynısını yapıyoruz ama içine 
    // "ThemeData.dark().textTheme" veriyoruz ki yazı renkleri beyaz kalsın.
    textTheme: GoogleFonts.latoTextTheme(
      ThemeData.dark().textTheme,
    ),

    appBarTheme: const AppBarTheme(
      backgroundColor: _darkBackground,
      foregroundColor: _darkPrimaryColor,
      centerTitle: true,
    ),
  );
}

Adım 3: Özel Başlıklar İçin Override (Ezme) İşlemi

Bazen genel yazı tipi "Lato" olsun ama Ana Başlıklar (Headline) daha sanatsal bir font (örneğin "Bebas Neue") olsun isteyebilirsiniz.

Bunu yapmak için app_theme.dart içindeki textTheme kısmını şöyle güncelleyebilirsiniz:

Dart:
    textTheme: GoogleFonts.latoTextTheme(
      ThemeData.light().textTheme,
    ).copyWith(
      // Sadece en büyük başlığı farklı bir font yapalım:
      displayLarge: GoogleFonts.bebasNeue(
        fontSize: 48,
        fontWeight: FontWeight.bold,
        color: _lightPrimaryColor,
      ),
      // Diğerleri Lato kalmaya devam eder.
    ),

Öğrenciler İçin "Pardus/Okul" İpucu (İnternet Kısıtlaması)

Bu paket normalde uygulamayı ilk açtığınızda fontu internetten indirir ve önbelleğe (cache) alır. Ancak okullardaki Fatih ağı veya kısıtlı internet durumlarında sorun yaşamamak için fontları çevrimdışı (offline) kullanmak isteyebilirsiniz.

Bunun için öğrencilerinize şu yöntemi gösterebilirsiniz:

  1. fonts.google.com adresinden fontu indirin (Örn: Lato.zip).

  2. Proje içinde assets/fonts klasörü oluşturup .ttf dosyalarını buraya atın.

  3. pubspec.yaml dosyasına şu şekilde ekleyin:

YAML
assets:
  - assets/fonts/
  1. Artık paket internete ihtiyaç duymadan yerel dosyayı kullanacaktır (Paket otomatik olarak asset klasörüne bakar, ekstra kod yazmaya gerek kalmaz).

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