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
Textwidget'ı içinstyle: 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:
MaterialApp(
title: 'Flutter Tema Rehberi',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), // Otomatik palet oluşturur
),
home: AnaSayfa(),
);
İpucu:
ColorScheme.fromSeedmetodu, 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.
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.
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.
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.
// 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.
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ı
Material 3'ü Kucaklayın:
useMaterial3: trueile uygulamanızı geleceğe hazırlayın.Hard-Code Yapmayın:
Colors.blueyerineTheme.of(context).colorScheme.primarykullanın.FromSeed Kullanın: Renk uyumu konusunda uzman değilseniz,
fromSeedmetodu hayat kurtarıcıdır.Parçalara Bölün:
main.dartdosyanızın şişmesini istemiyorsanız, temanızıtheme.dartgibi 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:
Ana renk belirle
Yazı stilini ayarla
AppBar ve Button temasını oluştur
Dark mode ekle
📌 Kazanımlar:
UI standardı
Context bilinci
Material tasarım
Özet
| Özellik | Amaç |
|---|---|
| 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.
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:
ValueNotifierNedir?: Basit verileri (buradaThemeMode) saklayan ve değiştiğinde haber veren Flutter'ın yerleşik bir sınıfıdır.ValueListenableBuilder: Bu widget, sadece ilgili veriyi dinler. Tüm uygulamayı yeniden çizmek yerine (setStategibi), sadeceMaterialApp'in tema kısmını günceller. Performans dostudur.Dinamik Renkler:
ScaffoldveyaTextiçinde renkleri sabit (Colors.red) vermedik. HepTheme.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:
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.
mainfonksiyonuasyncolacak: Çünkü diskten okuma işlemi zaman alır.WidgetsFlutterBinding: Flutter motorunu manuel başlatacağız.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.
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:
Neden
asyncmain?: Normaldevoid 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çinawaitkullanıyoruz.WidgetsFlutterBinding: Flutter'ın arayüzü çizmeye başlamadan önce C++ motoruyla (engine) iletişim kurmasını sağlar.SharedPreferencesnative (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.Null Kontrolü:
TemaHafizasi.oku()fonksiyonunulldö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.
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
ThemeManagerveSharedPreferencesyapısının var olduğunu varsayıyorum. SadeceMaterialAppkısmını gösteriyorum:
// İ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:
Düzen: "Odanızdaki her şeyi (kıyafetler, kitaplar, tabaklar) yere atmak yerine; kıyafetleri dolaba, kitapları rafa koyduk.
main.dartbizim salonumuz, orayı temiz tutmalıyız."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.dartdosyasına gidip tek bir satırı değiştireceğiz."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:
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.
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:
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:
fonts.google.com adresinden fontu indirin (Örn: Lato.zip).
Proje içinde
assets/fontsklasörü oluşturup.ttfdosyalarını buraya atın.pubspec.yamldosyasına şu şekilde ekleyin:
assets:
- assets/fonts/
Artık paket internete ihtiyaç duymadan yerel dosyayı kullanacaktır (Paket otomatik olarak asset klasörüne bakar, ekstra kod yazmaya gerek kalmaz).
Yorumlar
Yorum Gönder