Tema dan Dark Mode di Flutter
Bangun sistem tema terpusat dengan ThemeData, dukung dark mode, dan pakai warna yang adaptif.
Kenapa tema terpusat?
Menulis TextStyle(color: Colors.teal) di 50 tempat berarti 50 tempat harus diubah saat rebranding. ThemeData memusatkan warna, tipografi, dan bentuk komponen di satu tempat. Widget tinggal memakai Theme.of(context).
Mendefinisikan light dan dark theme
final temaTerang = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.teal,
brightness: Brightness.light,
),
);
final temaGelap = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.teal,
brightness: Brightness.dark,
),
);
MaterialApp(
theme: temaTerang,
darkTheme: temaGelap,
themeMode: ThemeMode.system, // ikut pengaturan HP
)ColorScheme.fromSeed men-generate palet harmonis dari satu warna seed. ThemeMode.system mengikuti pengaturan HP user, light dan dark memaksa salah satu.
Toggle manual dengan state
Untuk tombol ganti tema di pengaturan, simpan pilihan user di state management:
final themeModeProvider =
NotifierProvider<ThemeModeNotifier, ThemeMode>(ThemeModeNotifier.new);
class ThemeModeNotifier extends Notifier<ThemeMode> {
@override
ThemeMode build() => ThemeMode.system;
void setMode(ThemeMode mode) => state = mode;
}
// Di MaterialApp
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final mode = ref.watch(themeModeProvider);
return MaterialApp(
theme: temaTerang,
darkTheme: temaGelap,
themeMode: mode,
home: const HalamanUtama(),
);
}
}Memakai warna tema di widget
Jangan hardcode warna. Ambil dari tema agar otomatis adaptif:
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Container(
color: scheme.primaryContainer,
child: Text(
'Kartu adaptif',
style: TextStyle(color: scheme.onPrimaryContainer),
),
);
}primaryContainer dengan onPrimaryContainer adalah pasangan yang dijamin kontras di kedua mode. Hindari Colors.white hardcode untuk background kartu karena akan menyilaukan di dark mode.
Kustomisasi komponen
final temaTerang = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 14),
),
),
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
);Satu definisi mengubah semua ElevatedButton dan TextField di aplikasi. Ini kekuatan tema yang sebenarnya.
Catatan teknis: Mengganti
themeModeme-rebuild seluruh MaterialApp, jadi pastikan tidak ada state penting yang hanya disimpan di widget di bawah MaterialApp tanpa state management. Juga, gambar atau warna hardcode di asset tidak ikut tema, siapkan varian asset atau pakai warna tema untuk overlay. Test kedua mode secara manual, karena kontras yang aman di light mode belum tentu aman di dark mode.