Bonjour à tous et bienvenue dans ce nouveau tutoriel Flutter dark mode sur la mise en place d’un thème sombre.

Le dark mode étant maintenant largement répandu sur iOS et Android, la plupart des applications permettent elles-aussi de changer de thème.

Nous allons voir aujourd’hui comment proposer à nos utilisateurs de changer manuellement le thème de leur application.

Pour cela nous utiliserons le package adaptive_theme qui nous permettra aussi de détecter le thème actuel de l’appareil.

Enfin nous verrons comment détecter quel thème vient d’être choisir pour afficher par exemple une icône différente pour le thème sombre ou clair.

Flutter Dark Mode Tuto

Pour créer un thème dans notre application Flutter nous allons suivre les étapes suivantes:

  1. Installer le package Flutter Dark Mode
  2. Configurer le Dark Mode dans Flutter
  3. Activer manuellement le thème sombre dans Flutter
  4. Détecter le thème sombre dans Flutter
  5. Créer un Toggle Flutter pour le Dark Mode

Vous pouvez également télécharger le code source de l’application développée dans ce tutoriel.

1. Installer le package Flutter Dark Mode

Notre tutoriel sur Dark Mode sera donc essentiellement basé sur l'utilisation d'un package Flutter.

Il s'agit du package adaptive_theme dont je vous laisse la documentation: https://pub.dev/packages/adaptive_theme

Vous pouvez donc commencer par ajouter la dépendance de ce package dans votre fichier pubspec.yaml:

adaptive_theme: ^1.1.0

Entrez ensuite la commande suivante pour télécharger le package dans votre application:

flutter pub get

Vous pouvez enfin importer ce package Flutter dans le fichier Dart de votre choix:

import 'package:adaptive_theme/adaptive_theme.dart';

Personnellement je l'importe pour commencer dans le fichier main.dart pour déclarer mon application MyApp avec le widget adapté.

2. Configurer le Dark Mode dans Flutter

On rentre maintenant dans le cœur de notre tutoriel Flutter Dark Mode, avec la configuration de notre thème sombre.

Concrètement, le package adaptive_theme nous donne accès à un widget Flutter préconçu AdaptiveTheme().

Ce widget possède différents champs, qui nous permettre de définir l'apparence précise du thème clair et sombre, ainsi que le thème initial:

AdaptiveTheme(
  light: ThemeData(...),
  dark: ThemeData(...),
  initial: AdaptiveThemeMode.light,
  builder: (theme, darkTheme) => MaterialApp(),
)

Juste avant de voir en détail l'utilisation de ce widget, je vous montre simplement à quel endroit il s'intègre dans votre code.

Dans votre fichier main.dart, vous pouvez importer les packages material et adaptive_theme et déclarer la fonction haute main().

Mais en ce qui concerne votre widget MyApp, construisez plutôt votre application directement avec le widget AdaptiveTheme():

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AdaptiveTheme();
  }
}

C'est ensuite à l'intérieur de ce widget AdaptiveTheme() que nous construirons notre application avec le widget plus classique MaterialApp().

En ce qui concerne les différents champs de notre widget AdaptiveTheme(), on retrouve ceux dédiés à l'apparence du thème sombre et clair.

On peut ainsi déclarer avec la classe ThemeData() l'apparence précise de notre thème couleur, en précisant plusieurs paramètres.

On indique pour commencer la luminosité (brightnes) de notre thème, dark ou light, pour faire le plus gros de notre application.

De cette manière, le fond de notre application sera en blanc ou en noir, le texte inversement, etc.

Mais on peut aussi modifier la couleur principale (primarySwatch) de notre thème, qui peut être de cette manière différente pour le thème clair ou sombre:

ThemeData(
  brightness: Brightness.light,
  primarySwatch: Colors.red,
  accentColor: Colors.amber,
),

Vous pouvez également modifier la couleur secondaire (accentColor) pour différencier encore plus vos différents thèmes.

On peut continuer en précisant le thème par défaut de notre application, par exemple au mode light:

initial: AdaptiveThemeMode.light,

Mais vous pouvez aussi à ce stade du tutoriel, de changer la valeur à dark et à rafraichir votre application:

initial: AdaptiveThemeMode.dark,

Votre application devrait alors passer à un thème sombre après rafraichissement.

Bien évidement, vous devez toujours utiliser un widget comme MaterialApp() pour construire le contenu de votre application:

MaterialApp(
  title: 'Adaptive Theme Demo',
  theme: theme,
  darkTheme: darkTheme,
  home: MyHomePage(),
),

Pour le contenu de votre widget MyHomePage(), prenez l'un des exemples de base de Flutter.

Pour récapituler, voilà le contenu de notre widget AdaptiveTheme() qui fait donc le plus du travail pour le thème sombre:

AdaptiveTheme(
  light: ThemeData(
    brightness: Brightness.light,
    primarySwatch: Colors.red,
    accentColor: Colors.amber,
  ),
  dark: ThemeData(
    brightness: Brightness.dark,
    primarySwatch: Colors.red,
    accentColor: Colors.amber,
  ),
  initial: AdaptiveThemeMode.light,
  builder: (theme, darkTheme) => MaterialApp(
    title: 'Adaptive Theme Demo',
    theme: theme,
    darkTheme: darkTheme,
    home: MyHomePage(),
  ),
)

Nous allons maintenant voir comment activer manuellement le thème sombre et le changer sur demande.

3. Activer manuellement le thème sombre dans Flutter

Pour changer le thème de notre application, le package adaptive_theme nous propose différentes fonctions très utile.

On peut par exemple choisir de passer manuellement notre application au thème sombre en exécutant le code suivant:

AdaptiveTheme.of(context).setDark();

Et de la même manière, on peut lui demander de passer au thème clair avec cette ligne:

AdaptiveTheme.of(context).setLight();

On peut également lui demander d'appliquer le même thème que celui de l'appareil qui est utilisé:

AdaptiveTheme.of(context).setSystem();

Et enfin on peut tout simplement lui demander l'alterner avec l'autre thème avec cette dernière fonction toggleThemeMode():

AdaptiveTheme.of(context).toggleThemeMode();

Bref, toutes ces fonctions nous permette de jouer facilement avec le thème de notre application Flutter.

Vous pouvez donc effectuer différent test en créant notamment un simple button que vous associez à ces différentes fonctions:

ElevatedButton(
  onPressed: () {
    AdaptiveTheme.of(context).toggleThemeMode();
  },
  child: Text('Changer le thème'),
),

Ici par exemple, je fais un test avec la fonction toggleThemeMode() qui me permet d'alterner entre les thèmes sombre et clair.

Flutter Dark Mode Button

À vous de faire votre choix, nous allons ensuite voir détecter le thème déjà présent dans notre application.

4. Détecter le thème sombre dans Flutter

On continue avec la fonctionnalité qui permet de détecter le thème actuel de notre application.

Cela peut être utile pour afficher certaines informations en fonction du thème, comme une icône différente.

Si on reprend notre widget MyHomePage(), il faudra faire en sorte de le déclarer en tant que StatefulWidget avec la syntaxe suivante:

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

Nous serons de cette manière capable rafraichir son contenu et plus précisément notre icône à chaque changement de thème.

On peut ensuite déclarer nos premières variables pour notre widget, qui nous permettront de stocker les informations sur le thème de l'application.

On déclarer une variable booléenne (true ou false) pour stocker si oui ou non le thème sombre est activé.

Et on déclare juste une variable intermédiaire savedThemeMode pour stocker le résultat qu package AdaptiveTheme():

class _MyHomePageState extends State<MyHomePage> {
  bool darkmode = false;
  dynamic savedThemeMode;

Pour détecter le thème actuel de l'application, on nous allons créer la fonction dédié getCurrentTheme() que nous appellerons dans la méthode initState():

void initState() {
  super.initState();
  getCurrentTheme();
}

De cette manière, notre fonction s'exécutera dès le chargement de notre widget.

En ce qui concerne de son code, il est directement basé sur la fonction getThemeMode() du package adaptive_theme.

On fait une simple vérification au niveau de la valeur stockée, et en fonction on affecte à notre variable darkmode la valeur true ou false:

Future getCurrentTheme() async {
  savedThemeMode = await AdaptiveTheme.getThemeMode();
  if (savedThemeMode.toString() == 'AdaptiveThemeMode.dark') {
    print('mode sombre');
    setState(() {
      darkmode = true;
    });
  } else {
    setState(() {
      darkmode = false;
    });
    print('mode clair');
  }
}

Il est important de contenir la ligne d'affectation de la variable darkmode à l'intérieur de la méthode setState().

De cette manière, notre page sera rafraîchie au changement de thème, et on pourra afficher la bonne icône.

Justement en parlant d'icône, je vous propose les deux images ci-dessous que vous pouvez télécharger:

Dans votre application Flutter, créez un dossier 'assets' et un sous-dossier 'icon'. Placez ensuite vos deux images à l'intérieur.

Pour pouvoir les utiliser dans votre application, vous devez déclarer leur référence dans le fichier Yaml comme ceci:

assets:
- assets/icon/ligth-icon.png
- assets/icon/dark-icon.png

Désormais, nous pouvons faire appel à nos deux images et les afficher dans le contenu de notre application.

Pour gérer l'affichage alterné de nos deux icônes, on peut créer une variable pour stocker l'adresse qui va varier:

class _MyHomePageState extends State<MyHomePage> {
  bool darkmode = false;
  dynamic savedThemeMode;
  String iconAdress;

Ainsi, on pourra déclarer pour le thème sombre l'adresse de l'icône dark-icon.png:

iconAdress = 'assets/icon/dark-icon.png';

Et pour le thème clair changer la valeur de l'adresse de l'icône à:

iconAdress = 'assets/icon/ligth-icon.png';

Bref, on peut reprendre le code de notre fonction getCurrentTheme() et ajouter ces deux lignes à l'endroit associé:

Future getCurrentTheme() async {
  savedThemeMode = await AdaptiveTheme.getThemeMode();
  if (savedThemeMode.toString() == 'AdaptiveThemeMode.dark') {
    print('mode sombre');
    setState(() {
      darkmode = true;
      iconAdress = 'assets/icon/dark-icon.png';
    });
  } else {
    setState(() {
      darkmode = false;
      iconAdress = 'assets/icon/ligth-icon.png';
    });
    print('mode clair');
  }
}

Il ne nous reste plus qu'à afficher notre image dans le contenu de notre page.

Pour cela on peut utiliser le widget Image.asset() avec l'adresse de l'image directement.

Mais comme cette adresse est soumise aux variations, le temps de chargement elle pourra être vide.

On utilise la syntaxe Flutter pour créer une condition d'affichage en fonction du contenu de la variable iconAdress:

iconAdress != null ? Image.asset(iconAdress) : Container()

Si elle n'est pas vide, on affiche bien notre image, sinon on renvoie un Container() vide.

Vous pouvez donc tester l'affichage de votre icône en changeant de thème et en rafraichissant l'application.

Flutter Dark Mode Detection

Car pour le moment, l'icône de change que lorsque l'on recharge l'application, étant donné que le changement de variable se fait dans la méthode initState().

Nous allons donc finir par la création d'un toggle pour alterner entre les deux thèmes, et actualiser notre icône.

5. Créer un Toggle Flutter pour le Dark Mode

On termine donc ce tutoriel Flutter sur le dark mode avec la création d'un bouton toggle pour changer de thème.

C'est un très bon exercice pour récupérer la valeur du thème et être capable de la modifier.

Nous verrons aussi comment actualiser le contenu de notre page à chaque changement de thème.

Pour créer notre toggle, on peut utiliser différent widget, notamment le SwitchListTile() qui est présent par défaut.

Ce widget propose tous les champs nécessaires pour réaliser notre toggle de thème sombre.

Voilà d'ailleurs le résultat final de notre widget SwitchListTile() que nous allons détailler:

SwitchListTile(
  title: Text('Mode sombre'),
  value: darkmode,
  activeColor: Colors.orange,
  onChanged: (bool value) {
    print(value);
    if (value == true) {
      AdaptiveTheme.of(context).setDark();
      iconAdress = 'assets/icon/dark-icon.png';
    } else {
      AdaptiveTheme.of(context).setLight();
      iconAdress = 'assets/icon/ligth-icon.png';
    }
    setState(() {
      darkmode = value;
    });
  },
  secondary: const Icon(Icons.nightlight_round),
),

Le champ le plus important de notre widget est le champ value, car c'est lui qui associe notre toggle à notre variable booléenne.

On connecte donc notre toggle à notre variable darkmode de type booléen:

value: darkmode,

La deuxième partie la plus importante concerne l'évènement onChanged, qui s'exécute lorsqu'on clique sur le toggle et qu'on change sa valeur.

Cet évènement renvoie un booléen appelé ici value, que vous pouvez commencer par afficher dans la console.

onChanged: (bool value) {
  print(value);
},

L'idée, c'est qu'en fonction de la valeur renvoyé, nous allons mettre à jour le thème de l'application et changer la valeur de l'icône.

On peut donc effectuer la vérification suivante et exécuter les lignes de code suivantes:

 
if (value == true) {
  AdaptiveTheme.of(context).setDark();
  iconAdress = 'assets/icon/dark-icon.png';
} else {
  AdaptiveTheme.of(context).setLight();
  iconAdress = 'assets/icon/ligth-icon.png';
}

Bien sûr, il nous faut également mettre à jour la valeur de notre variable darkmode.

Nous plaçons cette affectation dans la méthode setState() recharger également notre application:

setState(() {
  darkmode = value;
});

De cette manière, à chaque changement de notre toggle, on modifie le thème et l'icône de notre application.

Mais on fait aussi en sorte que le changement de notre icône soit instantané.

Flutter Dark Mode Toggle

Pour finir ce tutoriel Flutter sur le dark mode, vous pouvez utiliser d'autres widgets toggle personnalisable.

J'ai par exemple testé le widget sliding_switch dont je vous laisse la documentation: https://pub.dev/packages/sliding_switch

sliding switch

Attention toutefois avec ce package, il n'est pas forcément à jour avec les dernières versions de Flutter.

Donc dans le doute, cherchez également d'autres widget en tapant 'toggle' ou 'switch' dans la barre de recherche du site: https://pub.dev/