ThemeData & Material 3
Tema global: warna, tipografi, dan ColorScheme.
Masalah yang Diselesaikan ThemeData
Di awal project, warna biasanya ditulis langsung di widget: Colors.purple di AppBar, Color(0xFF7C3AED) di tombol, Colors.grey[200] di kartu. Lama-lama ada puluhan varian ungu yang mirip tapi tidak sama, dan saat user minta ganti warna brand, kamu harus berburu satu per satu ke seluruh file. ThemeData menyelesaikan ini dengan satu sumber kebenaran: semua warna, bentuk, dan tipografi didefinisikan sekali di MaterialApp, lalu setiap widget mengambilnya lewat Theme.of(context). Ganti brand berarti edit satu tempat.
Seed Color: Palet Otomatis
Cara termudah membangun tema Material 3 adalah dari satu warna seed. Flutter menurunkan seluruh ColorScheme (primary, secondary, surface, error, dan varian terang/gelapnya) yang harmonis secara otomatis:
MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
appBarTheme: const AppBarTheme(centerTitle: true),
),
home: const HomePage(),
)Ini jauh lebih aman daripada meracik tiap warna manual, karena Flutter sudah menghitung kontras yang memenuhi standar aksesibilitas antara teks dan background.
Tema Komponen: Satu Tempat untuk Semua
Untuk aplikasi nyata, kamu ingin tombol, input, dan kartu konsisten tanpa mengulang style di tiap layar. Definisikan di ThemeData:
final temaWarung = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
appBarTheme: const AppBarTheme(centerTitle: true),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 24),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
filled: true,
),
cardTheme: CardThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 2,
),
textTheme: const TextTheme(
headlineSmall: TextStyle(fontWeight: FontWeight.bold),
),
);Lalu di widget, jangan pernah hardcode. Ambil dari tema:
Container(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: Text('Total', style: Theme.of(context).textTheme.headlineSmall),
)Edge Cases yang Perlu Diwaspadai
Pertama, Theme.of(context) hanya bisa dipanggil dari build (atau setelahnya). Memanggilnya di initState akan throw karena widget belum terpasang di tree; kalau butuh tema saat inisialisasi, pindah ke didChangeDependencies. Kedua, dialog dan bottom sheet mewarisi tema dari context yang memanggil showDialog, jadi kalau kamu memanggilnya dari context yang dibungkus Theme kustom, dialog ikut kustom (kadang ini diinginkan, kadang mengejutkan). Ketiga, untuk override lokal satu subtree saja, bungkus dengan widget Theme(data: Theme.of(context).copyWith(...), child: ...), jangan duplikasi seluruh ThemeData. Keempat, ColorScheme.fromSeed dengan brightness: Brightness.dark menghasilkan palet gelap yang berbeda (bukan sekadar versi gelap dari palet terang), jadi selalu definisikan darkTheme terpisah dan uji keduanya.
Kapan TIDAK Mengandalkan ThemeData
Untuk satu layar yang sangat artistik dan sekali pakai (misalnya splash screen ilustrasi atau halaman promo dengan gaya unik), memaksakan semuanya lewat tema global justru bikin ThemeData kotor dengan style yang tidak dipakai di tempat lain. Di kasus itu, style lokal dengan konstanta di file layar tersebut lebih jujur. Juga, widget pihak ketiga yang membawa sistem theming sendiri kadang perlu dibungkus atau di-override secara khusus, jangan dilawan dengan copyWith berlapis-lapis.
Catatan teknis:
Theme.of(context)memakaiInheritedWidget, jadi widget yang memanggilnya otomatis rebuild saat tema berubah. Inilah alasan dark mode bisa bekerja tanpa mengubah satu pun widget: cukup gantiThemeDatadiMaterialApp, seluruh tree ikut diperbarui.
Tantangan
Tema warung
Buat ThemeData dengan seedColor coklat (Colors.brown), AppBar centerTitle true, dan semua ElevatedButton rounded 12. Terapkan di MaterialApp.