Gestione dei temi in Flutter con ThemeData e dark mode dinamica
GuideIntermedio35 min Flutter 3.x

Gestione dei temi in Flutter con ThemeData e dark mode dinamica

Temi e Dark Mode in Flutter

Un'app moderna deve adattarsi alle preferenze dell'utente, offrendo sia una modalità chiara che una scura. Flutter rende tutto questo semplice grazie a ThemeData, ColorScheme e al widget MaterialApp.

In questo tutorial costruiremo un'app con un tema chiaro e uno scuro completamente personalizzati, e aggiungeremo un interruttore che permette all'utente di cambiare tema in tempo reale. Vedremo anche come seguire automaticamente l'impostazione di sistema.

Al termine avrai una struttura pulita e riutilizzabile per la gestione dei temi nei tuoi progetti.

  1. 1

    Creare il progetto e capire il punto di partenza

    Iniziamo creando un nuovo progetto Flutter. Il widget MaterialApp espone tre proprietà fondamentali per la gestione dei temi:

    • theme: il tema usato in modalità chiara
    • darkTheme: il tema usato in modalità scura
    • themeMode: decide quale dei due applicare (light, dark o system)

    Quando themeMode è impostato su ThemeMode.system, Flutter segue automaticamente le impostazioni del dispositivo.

    flutter create temi_app
    cd temi_app

    Risultato atteso

    Un progetto Flutter funzionante pronto per essere modificato.

  2. 2

    Definire i temi chiaro e scuro in un file dedicato

    Per mantenere il codice ordinato, centralizziamo i temi in un file separato lib/app_theme.dart. Usiamo ColorScheme.fromSeed che genera automaticamente una palette coerente a partire da un colore base, sia per il tema chiaro che per quello scuro.

    import 'package:flutter/material.dart';
    
    class AppTheme {
      static const Color _seedColor = Colors.indigo;
    
      static ThemeData get light => ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(
              seedColor: _seedColor,
              brightness: Brightness.light,
            ),
            appBarTheme: const AppBarTheme(centerTitle: true),
          );
    
      static ThemeData get dark => ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(
              seedColor: _seedColor,
              brightness: Brightness.dark,
            ),
            appBarTheme: const AppBarTheme(centerTitle: true),
          );
    }

    Risultato atteso

    Due temi pronti all'uso, accessibili tramite AppTheme.light e AppTheme.dark.

  3. 3

    Creare un controller per gestire lo stato del tema

    Per cambiare tema dinamicamente abbiamo bisogno di uno stato condiviso. Usiamo un ChangeNotifier semplice, senza pacchetti esterni, sfruttando ValueNotifier oppure una classe dedicata. Qui creiamo un ThemeController che memorizza il ThemeMode corrente e notifica i listener al cambiamento.

    import 'package:flutter/material.dart';
    
    class ThemeController extends ChangeNotifier {
      ThemeMode _themeMode = ThemeMode.system;
    
      ThemeMode get themeMode => _themeMode;
    
      bool get isDark => _themeMode == ThemeMode.dark;
    
      void setMode(ThemeMode mode) {
        _themeMode = mode;
        notifyListeners();
      }
    
      void toggleDark(bool enabled) {
        _themeMode = enabled ? ThemeMode.dark : ThemeMode.light;
        notifyListeners();
      }
    }

    Risultato atteso

    Un controller riutilizzabile che gestisce e notifica i cambi di tema.

  4. 4

    Collegare il controller a MaterialApp

    Nel main.dart istanziamo il ThemeController e usiamo AnimatedBuilder (o ListenableBuilder da Flutter 3.10+) per ricostruire MaterialApp ogni volta che il tema cambia. Passiamo i temi definiti in precedenza alle proprietà theme, darkTheme e themeMode.

    import 'package:flutter/material.dart';
    import 'app_theme.dart';
    import 'theme_controller.dart';
    import 'home_page.dart';
    
    final themeController = ThemeController();
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return ListenableBuilder(
          listenable: themeController,
          builder: (context, _) {
            return MaterialApp(
              title: 'Temi App',
              theme: AppTheme.light,
              darkTheme: AppTheme.dark,
              themeMode: themeController.themeMode,
              home: const HomePage(),
            );
          },
        );
      }
    }

    Risultato atteso

    L'app si avvia seguendo il tema di sistema e si aggiorna quando cambia il themeMode.

  5. 5

    Aggiungere il toggle per cambiare tema

    Creiamo la HomePage con uno SwitchListTile per attivare la dark mode e un gruppo di scelte per selezionare la modalità (chiaro, scuro o sistema). I colori dei widget si adatteranno automaticamente perché usano il ColorScheme del tema attivo.

    import 'package:flutter/material.dart';
    import 'main.dart';
    
    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        final mode = themeController.themeMode;
        return Scaffold(
          appBar: AppBar(title: const Text('Gestione Temi')),
          body: ListView(
            padding: const EdgeInsets.all(16),
            children: [
              SwitchListTile(
                title: const Text('Modalità scura'),
                value: themeController.isDark,
                onChanged: themeController.toggleDark,
              ),
              const Divider(),
              RadioListTile<ThemeMode>(
                title: const Text('Sistema'),
                value: ThemeMode.system,
                groupValue: mode,
                onChanged: (v) => themeController.setMode(v!),
              ),
              RadioListTile<ThemeMode>(
                title: const Text('Chiaro'),
                value: ThemeMode.light,
                groupValue: mode,
                onChanged: (v) => themeController.setMode(v!),
              ),
              RadioListTile<ThemeMode>(
                title: const Text('Scuro'),
                value: ThemeMode.dark,
                groupValue: mode,
                onChanged: (v) => themeController.setMode(v!),
              ),
              const SizedBox(height: 24),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Text(
                    'Esempio di contenuto che cambia colore con il tema.',
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }

    Risultato atteso

    Cambiando lo switch o la modalità l'intera interfaccia passa da chiaro a scuro istantaneamente.

  6. 6

    Personalizzare componenti con gli ThemeExtension

    Per colori personalizzati che non rientrano nel ColorScheme standard (es. il colore di un badge "successo"), Flutter offre ThemeExtension. In questo modo eviti di scrivere colori hard-coded nei widget e mantieni coerenza tra tema chiaro e scuro.

    @immutable
    class CustomColors extends ThemeExtension<CustomColors> {
      final Color success;
      const CustomColors({required this.success});
    
      @override
      CustomColors copyWith({Color? success}) =>
          CustomColors(success: success ?? this.success);
    
      @override
      CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
        if (other is! CustomColors) return this;
        return CustomColors(success: Color.lerp(success, other.success, t)!);
      }
    }
    
    // Uso nei temi:
    // extensions: const [CustomColors(success: Colors.green)]
    // extensions: const [CustomColors(success: Color(0xFF66BB6A))]
    //
    // Lettura in un widget:
    final custom = Theme.of(context).extension<CustomColors>()!;

    Risultato atteso

    Puoi accedere a colori personalizzati coerenti col tema tramite Theme.of(context).extension<CustomColors>().

  7. 7

    Rendere persistente la scelta dell'utente

    Infine, salviamo la preferenza dell'utente così che venga ricordata al riavvio dell'app. Usiamo shared_preferences per memorizzare il ThemeMode selezionato e lo carichiamo all'avvio. Aggiungi la dipendenza con flutter pub add shared_preferences.

    import 'package:shared_preferences/shared_preferences.dart';
    import 'package:flutter/material.dart';
    
    class ThemeController extends ChangeNotifier {
      ThemeMode _themeMode = ThemeMode.system;
      ThemeMode get themeMode => _themeMode;
      bool get isDark => _themeMode == ThemeMode.dark;
    
      Future<void> load() async {
        final prefs = await SharedPreferences.getInstance();
        final index = prefs.getInt('themeMode') ?? ThemeMode.system.index;
        _themeMode = ThemeMode.values[index];
        notifyListeners();
      }
    
      Future<void> setMode(ThemeMode mode) async {
        _themeMode = mode;
        notifyListeners();
        final prefs = await SharedPreferences.getInstance();
        await prefs.setInt('themeMode', mode.index);
      }
    
      void toggleDark(bool enabled) =>
          setMode(enabled ? ThemeMode.dark : ThemeMode.light);
    }
    
    // In main():
    // WidgetsFlutterBinding.ensureInitialized();
    // await themeController.load();
    // runApp(const MyApp());

    Risultato atteso

    Alla riapertura dell'app il tema scelto dall'utente viene ripristinato automaticamente.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!