CHAPITRE 1.2

Fondements du langage Dart

Apprenez les bases du langage Dart
Avant de plonger dans Flutter, il est essentiel de maîtriser le langage Dart, sur lequel Flutter est construit. Cette section vous initiera aux fondamentaux de Dart : les types de données, les variables, les fonctions, ainsi que les concepts de la programmation orientée objet (classes, objets, héritage). Vous apprendrez également les bonnes pratiques et créerez vos premiers programmes Dart.

1.2Fondements du langage Dart

1.2.1 – Types, variables, fonctions

Avant de créer des interfaces avec Flutter, il est essentiel de maîtriser les bases du langage Dart. Dans cette section, nous allons découvrir les types de données, comment déclarer des variables, et comment créer des fonctions. Ces concepts sont les fondations de tout programme Dart.

🔤 Les types de base en Dart

Dart est un langage typé. Cela signifie que chaque variable a un type qui détermine quel genre de données elle peut contenir. Commençons par les types les plus courants.

int - Les nombres entiers

Le type int représente les nombres entiers (sans décimales).

// Déclaration de nombres entiers
int age = 25;
int nombreEtudiants = 150;
int temperature = -5;

// Opérations mathématiques
int somme = 10 + 20;        // 30
int produit = 5 * 6;        // 30
int division = 15 ~/ 3;     // 5 (division entière)
int reste = 17 % 5;         // 2 (modulo)

double - Les nombres décimaux

Le type double représente les nombres à virgule flottante (avec décimales).

// Déclaration de nombres décimaux
double prix = 19.99;
double taille = 1.75;
double pi = 3.14159;

// Opérations
double total = prix * 2;    // 39.98
double moyenne = 15.5 / 2;  // 7.75
En Dart, utilisez ~/ pour une division entière et / pour une division décimale. C'est différent de langages comme Java où / fait une division entière entre deux int.

String - Les chaînes de caractères

Le type String représente du texte.

// Déclaration de chaînes
String prenom = 'Alice';
String nom = "Dupont";                    // Simple ou double guillemets
String message = 'Bonjour tout le monde!';

// Concaténation
String nomComplet = prenom + ' ' + nom;   // 'Alice Dupont'

// Interpolation (recommandé)
String presentation = 'Je m\'appelle $prenom';
String phrase = 'Bonjour $prenom $nom, bienvenue !';

// Interpolation avec expression
int age = 25;
String info = 'J\'ai $age ans, soit ${age * 12} mois';

// Chaînes multi-lignes
String description = '''
Ceci est un texte
sur plusieurs lignes.
C'est pratique pour les longs textes.
''';
L'interpolation avec $variable ou ${expression} est la méthode préférée en Dart. C'est plus lisible que la concaténation avec des +.

bool - Les booléens

Le type bool ne peut avoir que deux valeurs : true ou false.

// Déclaration de booléens
bool estConnecte = true;
bool aDesEnfants = false;

// Conditions
bool estMajeur = age >= 18;
bool estMineur = !estMajeur;  // Négation avec !

// Opérateurs logiques
bool condition1 = true && false;  // false (ET logique)
bool condition2 = true || false;  // true (OU logique)

num - Type générique pour les nombres

Le type num accepte à la fois les int et les double.

// num peut contenir int ou double
num nombre1 = 42;        // OK (int)
num nombre2 = 3.14;      // OK (double)
num somme = nombre1 + nombre2;  // OK

📦 Déclaration de variables

Dart offre plusieurs façons de déclarer des variables. Voyons les différentes options.

Typage explicite

Vous spécifiez le type de la variable lors de sa déclaration.

// Type explicite
String prenom = 'Marie';
int age = 30;
double taille = 1.68;
bool estEtudiant = true;

Inférence de type avec var

Dart peut déduire automatiquement le type à partir de la valeur assignée.

// Dart infère automatiquement le type
var prenom = 'Marie';        // Dart comprend que c'est un String
var age = 30;                // Dart comprend que c'est un int
var taille = 1.68;           // Dart comprend que c'est un double
var estEtudiant = true;      // Dart comprend que c'est un bool

// Une fois le type inféré, il ne peut plus changer
age = 31;           // ✅ OK (toujours un int)
// age = 'trente';  // ❌ ERREUR (ne peut pas changer de type)

Type dynamic

Le type dynamic permet de créer des variables qui peuvent changer de type à l'exécution. C'est utile dans certains cas spécifiques, mais doit être utilisé avec précaution car il désactive les vérifications de type.

// Variable dynamic peut changer de type
dynamic valeur = 42;           // Commence comme int
print(valeur);                 // 42

valeur = 'Bonjour';            // ✅ OK (devient String)
print(valeur);                 // Bonjour

valeur = 3.14;                 // ✅ OK (devient double)
print(valeur);                 // 3.14

valeur = true;                  // ✅ OK (devient bool)
print(valeur);                 // true

// Attention : pas de vérification de type à la compilation
dynamic nombre = 'texte';
// int resultat = nombre + 5;  // ❌ ERREUR à l'exécution (runtime error)

// Utilisation avec des APIs dynamiques (JSON, etc.)
dynamic donnees = {
  'nom': 'Alice',
  'age': 25,
  'actif': true,
};

String nom = donnees['nom'];      // OK
int age = donnees['age'];         // OK
bool actif = donnees['actif'];    // OK
Quand utiliser dynamic ?

• Travailler avec des données JSON non typées
• Interagir avec des APIs externes dont la structure est inconnue
• Migration de code : Lorsque vous modernisez un ancien projet ou du code dont les types ne sont pas clairement définis

Quand éviter dynamic ?

• Dans la plupart des cas, préférez des types explicites
dynamic désactive les vérifications de type et peut causer des erreurs à l'exécution
• Utilisez plutôt des types optionnels (String?, int?) ou des classes pour structurer vos données

Variables finales avec final

Le mot-clé final crée une variable qui ne peut être assignée qu'une seule fois.

// Variable finale (ne peut pas être réassignée)
final String ville = 'Paris';
final int anneeNaissance = 1995;

// Tentative de modification
// ville = 'Lyon';  // ❌ ERREUR : ne peut pas modifier une variable final

// final avec inférence de type
final pays = 'France';  // Le type String est inféré

Constantes avec const

Le mot-clé const crée une constante de compilation. La valeur doit être connue au moment de la compilation.

// Constantes de compilation
const double pi = 3.14159;
const int joursParSemaine = 7;
const String appName = 'Mon App Flutter';

// Différence entre final et const
final dateActuelle = DateTime.now();   // ✅ OK (valeur connue à l'exécution)
// const dateConst = DateTime.now();   // ❌ ERREUR (valeur pas connue à la compilation)
Quand utiliser quoi ?

var : Pour les variables normales qui peuvent changer
final : Pour les valeurs qui ne changeront pas après l'initialisation
const : Pour les constantes vraiment fixes (valeurs connues à la compilation)

En général, préférez final quand c'est possible. C'est une bonne pratique qui évite les bugs.

Variables nullables

En Dart moderne (depuis la version 2.12), les variables sont non-nullables par défaut. Pour accepter une valeur nulle, ajoutez ? au type.

// Variable non-nullable (par défaut)
String nom = 'Alice';
// nom = null;  // ❌ ERREUR : ne peut pas être null

// Variable nullable (avec ?)
String? nomOptional = 'Bob';
nomOptional = null;  // ✅ OK

// int nullable
int? age;  // Vaut null par défaut
age = 25;  // ✅ OK
age = null;  // ✅ OK aussi
La gestion stricte des valeurs nulles en Dart évite de nombreux bugs. Si une variable peut être nulle, vous devez le déclarer explicitement avec ?. C'est une protection importante contre les erreurs.

⚙️ Les fonctions en Dart

Les fonctions permettent de regrouper du code réutilisable. Dart offre une syntaxe simple et puissante pour créer des fonctions.

Fonction simple

// Fonction qui affiche un message
void direBonjour() {
  print('Bonjour !');
}

// Appel de la fonction
direBonjour();  // Affiche : Bonjour !

Fonction avec paramètres

// Fonction avec un paramètre
void saluer(String nom) {
  print('Bonjour $nom !');
}

// Fonction avec plusieurs paramètres
void presenter(String prenom, String nom, int age) {
  print('Je m\'appelle $prenom $nom et j\'ai $age ans.');
}

// Appels
saluer('Alice');                    // Bonjour Alice !
presenter('Bob', 'Martin', 30);     // Je m'appelle Bob Martin et j'ai 30 ans.

Fonction avec retour de valeur

// Fonction qui retourne un int
int additionner(int a, int b) {
  return a + b;
}

// Fonction qui retourne un String
String obtenirMessage() {
  return 'Ceci est un message';
}

// Fonction qui retourne un bool
bool estPair(int nombre) {
  return nombre % 2 == 0;
}

// Utilisation
int resultat = additionner(5, 3);      // 8
String msg = obtenirMessage();         // 'Ceci est un message'
bool pair = estPair(10);               // true

Fonction avec syntaxe courte (arrow function)

Pour les fonctions simples qui retournent une seule expression, Dart propose une syntaxe raccourcie avec =>.

// Syntaxe complète
int multiplier(int a, int b) {
  return a * b;
}

// Syntaxe courte (équivalent)
int multiplierCourt(int a, int b) => a * b;

// Autres exemples
String direBonjour(String nom) => 'Bonjour $nom !';
bool estPositif(int n) => n > 0;
double calculerCarre(double x) => x * x;

// Utilisation identique
int resultat = multiplierCourt(4, 5);  // 20
String msg = direBonjour('Alice');     // 'Bonjour Alice !'
La syntaxe courte => est très utilisée en Flutter pour les fonctions simples, notamment dans les callbacks. Elle rend le code plus concis et lisible.

Paramètres optionnels positionnels

Vous pouvez rendre certains paramètres optionnels en les plaçant entre crochets [].

// Paramètres optionnels entre crochets
void afficherInfos(String nom, [int? age]) {
  if (age != null) {
    print('$nom a $age ans');
  } else {
    print('Nom: $nom');
  }
}

// Appels possibles
afficherInfos('Alice');        // Nom: Alice
afficherInfos('Bob', 25);      // Bob a 25 ans

// Avec valeur par défaut
void saluer(String nom, [String salutation = 'Bonjour']) {
  print('$salutation $nom !');
}

saluer('Alice');               // Bonjour Alice !
saluer('Bob', 'Salut');        // Salut Bob !

Paramètres nommés

Les paramètres nommés permettent d'appeler une fonction en spécifiant explicitement le nom des paramètres. C'est très utilisé en Flutter.

// Paramètres nommés entre accolades
void creerUtilisateur({
  required String nom,      // required = obligatoire
  required String email,
  int age = 18,            // valeur par défaut
  String? ville,           // optionnel (peut être null)
}) {
  print('Utilisateur: $nom');
  print('Email: $email');
  print('Age: $age');
  if (ville != null) print('Ville: $ville');
}

// Appel avec paramètres nommés (ordre libre)
creerUtilisateur(
  nom: 'Alice',
  email: 'alice@exemple.com',
  ville: 'Paris',
);

// Autre ordre, même résultat
creerUtilisateur(
  email: 'bob@exemple.com',
  nom: 'Bob',
  age: 25,
);
Pourquoi utiliser des paramètres nommés ?

Imaginez une fonction avec beaucoup de paramètres :
creerBouton(true, 'OK', 16, Colors.blue, null, false)

Difficile de comprendre ce que représente chaque paramètre !

Avec des paramètres nommés :
creerBouton( actif: true, texte: 'OK', taille: 16, couleur: Colors.blue, )

Beaucoup plus clair ! C'est pourquoi Flutter utilise massivement cette syntaxe.

🎲 Types collections

List - Les listes

Une List est une collection ordonnée d'éléments.

// Liste de String
List<String> fruits = ['Pomme', 'Banane', 'Orange'];

// Avec inférence de type
var nombres = [1, 2, 3, 4, 5];

// Accès aux éléments (indexation à partir de 0)
String premierFruit = fruits[0];    // 'Pomme'
String dernierFruit = fruits[2];    // 'Orange'

// Propriétés utiles
int longueur = fruits.length;       // 3
bool estVide = fruits.isEmpty;      // false

// Ajouter des éléments
fruits.add('Kiwi');                 // Ajoute à la fin
fruits.insert(1, 'Mangue');         // Insère à l'index 1

// Retirer des éléments
fruits.remove('Banane');            // Retire 'Banane'
fruits.removeAt(0);                 // Retire l'élément à l'index 0

// Parcourir une liste
for (var fruit in fruits) {
  print(fruit);
}

Map - Les dictionnaires

Une Map associe des clés à des valeurs (comme un dictionnaire).

// Map de String vers String
Map<String, String> capitales = {
  'France': 'Paris',
  'Espagne': 'Madrid',
  'Italie': 'Rome',
};

// Avec inférence de type
var ages = {
  'Alice': 25,
  'Bob': 30,
  'Charlie': 28,
};

// Accès aux valeurs
String? capitale = capitales['France'];  // 'Paris'
int? ageAlice = ages['Alice'];           // 25

// Ajouter/Modifier
capitales['Allemagne'] = 'Berlin';       // Ajoute
ages['Alice'] = 26;                      // Modifie

// Vérifier l'existence d'une clé
bool existe = capitales.containsKey('France');  // true

// Parcourir un Map
capitales.forEach((pays, ville) {
  print('La capitale de $pays est $ville');
});

Set - Les ensembles

Un Set est une collection d'éléments uniques (pas de doublons).

// Set de String
Set<String> langages = {'Dart', 'Flutter', 'JavaScript'};

// Ajouter des éléments
langages.add('Python');
langages.add('Dart');  // Ignoré (déjà présent)

print(langages.length);  // 4 (pas 5, car Dart n'a pas été ajouté deux fois)
Utilisez List quand l'ordre est important et les doublons sont autorisés. Utilisez Set quand vous voulez éviter les doublons. Utilisez Map pour associer des clés à des valeurs.

🔄 Opérateurs et expressions

Opérateurs arithmétiques

int a = 10;
int b = 3;

int addition = a + b;       // 13
int soustraction = a - b;   // 7
int multiplication = a * b; // 30
double division = a / b;    // 3.333...
int divisionEntiere = a ~/ b;  // 3
int modulo = a % b;         // 1 (reste de la division)

// Incrémentation / Décrémentation
a++;  // a = 11
b--;  // b = 2

Opérateurs de comparaison

int x = 10;
int y = 20;

bool egal = x == y;           // false
bool different = x != y;      // true
bool inferieur = x < y;        // true
bool superieur = x > y;        // false
bool infEgal = x <= y;        // true
bool supEgal = x >= y;        // false

Opérateur null-aware

Dart fournit des opérateurs pratiques pour gérer les valeurs potentiellement nulles.

String? nomOptional = null;

// Opérateur ?? (valeur par défaut si null)
String nom = nomOptional ?? 'Anonyme';  // 'Anonyme' car nomOptional est null

// Opérateur ??= (assigner seulement si null)
String? prenom;
prenom ??= 'Jean';   // prenom devient 'Jean'
prenom ??= 'Paul';   // prenom reste 'Jean' (pas null)

// Opérateur ?. (accès conditionnel)
String? ville = null;
int? longueur = ville?.length;  // null (pas d'erreur grâce à ?.)

ville = 'Paris';
longueur = ville?.length;  // 5

🎯 Exemple pratique complet

Mettons en pratique ce que nous avons appris avec un exemple concret.

// Fonction pour calculer le prix TTC
double calculerPrixTTC(double prixHT, {double tauxTVA = 0.20}) {
  double montantTVA = prixHT * tauxTVA;
  double prixTTC = prixHT + montantTVA;
  return prixTTC;
}

// Fonction pour afficher un produit
void afficherProduit({
  required String nom,
  required double prix,
  String? description,
  bool enStock = true,
}) {
  print('=== Produit ===');
  print('Nom: $nom');
  print('Prix: ${prix.toStringAsFixed(2)} €');
  
  if (description != null) {
    print('Description: $description');
  }
  
  String statut = enStock ? 'En stock' : 'Rupture de stock';
  print('Statut: $statut');
}

// Fonction principale
void main() {
  // Variables
  var nomProduit = 'Ordinateur portable';
  var prixHT = 800.0;
  final tauxTVA = 0.20;
  
  // Calculs
  double prixTTC = calculerPrixTTC(prixHT, tauxTVA: tauxTVA);
  
  // Liste de produits
  List<String> categories = ['Électronique', 'Informatique', 'High-Tech'];
  
  // Affichage
  afficherProduit(
    nom: nomProduit,
    prix: prixTTC,
    description: 'PC portable 15 pouces, 16 Go RAM',
    enStock: true,
  );
  
  print('\nCatégories: ${categories.join(', ')}');
}
Sortie du programme :
=== Produit ===
Nom: Ordinateur portable
Prix: 960.00 €
Description: PC portable 15 pouces, 16 Go RAM
Statut: En stock

Catégories: Électronique, Informatique, High-Tech

💡 Points clés à retenir

  • Types de base : int, double, String, bool
  • Collections : List (listes), Map (dictionnaires), Set (ensembles)
  • Déclaration : var (inférence),dynamic (type variable), final (non modifiable), const (constante)
  • Nullabilité : Type? pour accepter null, opérateurs ?? et ?.
  • Fonctions : Paramètres positionnels, nommés, optionnels, syntaxe courte =>
  • Interpolation : $variable ou ${expression} dans les String
Ces concepts sont la base de tout code Dart et Flutter. Prenez le temps de bien les comprendre et de les pratiquer. Créez de petits programmes pour vous exercer avant de passer aux widgets Flutter.

1.2.2 – Classes, objets et OOP

Dart est un langage orienté objet. Cela signifie que tout est organisé autour du concept de "classes" et d'"objets". Dans cette section, nous allons découvrir comment créer et utiliser des classes, et comprendre les principes fondamentaux de la programmation orientée objet (OOP).

🏛️ Qu'est-ce qu'une classe ?

Une classe est un modèle (ou un plan) qui définit la structure et le comportement d'un type d'objet. Pensez à une classe comme à un moule à gâteau : elle définit la forme, mais ce n'est pas le gâteau lui-même.

Un objet est une instance concrète d'une classe. C'est le gâteau qui sort du moule.

Analogie :

• La classe "Voiture" définit ce qu'est une voiture (elle a une marque, un modèle, une couleur, elle peut rouler, s'arrêter, etc.)
• Un objet est une voiture spécifique : "une Toyota Corolla rouge de 2023"

📦 Créer une classe simple

// Définition d'une classe
class Personne {
  // Attributs (propriétés)
  String nom;
  String prenom;
  int age;
  
  // Constructeur
  Personne(this.nom, this.prenom, this.age);
  
  // Méthode (comportement)
  void sePresenter() {
    print('Bonjour, je m\'appelle $prenom $nom et j\'ai $age ans.');
  }
}

// Utilisation de la classe
void main() {
  // Créer un objet (instance de la classe)
  Personne alice = Personne('Dupont', 'Alice', 25);
  Personne bob = Personne('Martin', 'Bob', 30);
  
  // Appeler une méthode
  alice.sePresenter();  // Bonjour, je m'appelle Alice Dupont et j'ai 25 ans.
  bob.sePresenter();    // Bonjour, je m'appelle Bob Martin et j'ai 30 ans.
  
  // Accéder aux attributs
  print('${alice.prenom} a ${alice.age} ans');  // Alice a 25 ans
}

🔧 Les constructeurs

Un constructeur est une méthode spéciale qui permet de créer et d'initialiser un objet. Dart offre plusieurs façons de définir des constructeurs.

Constructeur simple

class Rectangle {
  double largeur;
  double hauteur;
  
  // Constructeur avec paramètres
  Rectangle(this.largeur, this.hauteur);
}

Constructeur avec paramètres nommés

class Utilisateur {
  String nom;
  String email;
  int age;
  
  // Constructeur avec paramètres nommés
  Utilisateur({
    required this.nom,
    required this.email,
    this.age = 18,  // Valeur par défaut
  });
}

// Utilisation
var user = Utilisateur(
  nom: 'Alice',
  email: 'alice@exemple.com',
  age: 25,
);
La syntaxe this.nom dans le constructeur est un raccourci Dart très pratique. Elle signifie "assigner automatiquement le paramètre nom à l'attribut this.nom". C'est équivalent à écrire this.nom = nom; dans le corps du constructeur.

Constructeurs nommés

Dart permet de créer plusieurs constructeurs pour une même classe en leur donnant des noms différents.

class Point {
  double x;
  double y;
  
  // Constructeur principal
  Point(this.x, this.y);
  
  // Constructeur nommé pour créer un point à l'origine
  Point.origine()
      : x = 0,
        y = 0;
  
  // Constructeur nommé pour créer un point depuis des coordonnées polaires
  Point.polaire(double rayon, double angle)
      : x = rayon * cos(angle),
        y = rayon * sin(angle);
}

// Utilisation
var p1 = Point(10, 20);           // Constructeur principal
var p2 = Point.origine();         // À l'origine (0, 0)
var p3 = Point.polaire(5, 45);    // Depuis coordonnées polaires

🔒 Encapsulation et propriétés privées

L'encapsulation est un principe de l'OOP qui consiste à cacher les détails internes d'une classe et à ne fournir qu'une interface publique. En Dart, on rend une propriété ou méthode privée en préfixant son nom par un underscore _.

class CompteBancaire {
  String titulaire;
  double _solde;  // Attribut privé (commence par _)
  
  CompteBancaire(this.titulaire, this._solde);
  
  // Getter pour accéder au solde (lecture seule)
  double get solde => _solde;
  
  // Méthode pour déposer de l'argent
  void deposer(double montant) {
    if (montant > 0) {
      _solde += montant;
      print('Dépôt de $montant € effectué. Nouveau solde: $_solde €');
    }
  }
  
  // Méthode pour retirer de l'argent
  void retirer(double montant) {
    if (montant > 0 && montant <= _solde) {
      _solde -= montant;
      print('Retrait de $montant € effectué. Nouveau solde: $_solde €');
    } else {
      print('Retrait impossible: solde insuffisant');
    }
  }
}

// Utilisation
void main() {
  var compte = CompteBancaire('Alice', 1000);
  
  print('Solde initial: ${compte.solde} €');  // ✅ OK (via getter)
  // compte._solde = 999999;  // ❌ ERREUR : propriété privée
  
  compte.deposer(500);   // Dépôt de 500 € effectué. Nouveau solde: 1500 €
  compte.retirer(200);   // Retrait de 200 € effectué. Nouveau solde: 1300 €
  compte.retirer(2000);  // Retrait impossible: solde insuffisant
}
En Dart, la visibilité privée est au niveau de la bibliothèque (fichier), pas de la classe. Cela signifie que tout code dans le même fichier peut accéder aux membres privés. Pour une vraie encapsulation, placez vos classes dans des fichiers séparés.

🎯 Getters et Setters

Les getters et setters sont des méthodes spéciales qui permettent de contrôler l'accès aux propriétés d'une classe.

class Rectangle {
  double _largeur;
  double _hauteur;
  
  Rectangle(this._largeur, this._hauteur);
  
  // Getter pour la largeur
  double get largeur => _largeur;
  
  // Setter pour la largeur avec validation
  set largeur(double valeur) {
    if (valeur > 0) {
      _largeur = valeur;
    } else {
      throw ArgumentError('La largeur doit être positive');
    }
  }
  
  // Getter calculé pour l'aire
  double get aire => _largeur * _hauteur;
  
  // Getter calculé pour le périmètre
  double get perimetre => 2 * (_largeur + _hauteur);
}

// Utilisation
void main() {
  var rect = Rectangle(10, 5);
  
  print('Largeur: ${rect.largeur}');      // 10
  print('Aire: ${rect.aire}');            // 50
  print('Périmètre: ${rect.perimetre}');  // 30
  
  rect.largeur = 15;  // Utilise le setter
  print('Nouvelle aire: ${rect.aire}');   // 75
  
  // rect.largeur = -5;  // ❌ Lève une exception
}
Utilisez les getters pour des propriétés calculées (comme aire et perimetre ci-dessus). C'est plus élégant que de créer des méthodes getAire() ou calculerPerimetre().

🧬 Héritage

L'héritage permet de créer une nouvelle classe basée sur une classe existante. La nouvelle classe (classe fille ou sous-classe) hérite des propriétés et méthodes de la classe parente (classe mère ou super-classe).

// Classe parente
class Animal {
  String nom;
  int age;
  
  Animal(this.nom, this.age);
  
  void manger() {
    print('$nom mange...');
  }
  
  void dormir() {
    print('$nom dort...');
  }
}

// Classe fille qui hérite de Animal
class Chien extends Animal {
  String race;
  
  // Constructeur qui appelle le constructeur parent
  Chien(String nom, int age, this.race) : super(nom, age);
  
  // Nouvelle méthode spécifique au chien
  void aboyer() {
    print('$nom aboie: Wouf Wouf!');
  }
  
  // Surcharge (override) d'une méthode parente
  @override
  void manger() {
    print('$nom (un $race) dévore ses croquettes!');
  }
}

// Utilisation
void main() {
  var chien = Chien('Rex', 3, 'Labrador');
  
  chien.manger();   // Rex (un Labrador) dévore ses croquettes!
  chien.dormir();   // Rex dort...
  chien.aboyer();   // Rex aboie: Wouf Wouf!
  
  print('${chien.nom} est un ${chien.race} de ${chien.age} ans');
}
L'annotation @override

L'annotation @override indique que vous redéfinissez intentionnellement une méthode de la classe parente. Ce n'est pas obligatoire, mais c'est une bonne pratique car :
• Cela rend le code plus lisible
• Dart vous avertira si vous faites une faute de frappe dans le nom de la méthode

🎭 Classes abstraites et interfaces

Une classe abstraite est une classe qui ne peut pas être instanciée directement. Elle sert de modèle pour d'autres classes.

// Classe abstraite
abstract class Forme {
  // Méthode abstraite (sans implémentation)
  double calculerAire();
  
  // Méthode concrète (avec implémentation)
  void afficher() {
    print('Aire: ${calculerAire()}');
  }
}

// Classe concrète qui implémente la méthode abstraite
class Cercle extends Forme {
  double rayon;
  
  Cercle(this.rayon);
  
  @override
  double calculerAire() {
    return 3.14159 * rayon * rayon;
  }
}

class Carre extends Forme {
  double cote;
  
  Carre(this.cote);
  
  @override
  double calculerAire() {
    return cote * cote;
  }
}

// Utilisation
void main() {
  // var forme = Forme();  // ❌ ERREUR : impossible d'instancier une classe abstraite
  
  Forme cercle = Cercle(5);
  Forme carre = Carre(4);
  
  cercle.afficher();  // Aire: 78.53975
  carre.afficher();   // Aire: 16.0
}

Implémenter une interface avec implements

En Dart, toute classe peut servir d'interface. Quand vous utilisez implements, vous devez réimplémenter toutes les méthodes de la classe.

// Classe utilisée comme interface
class Volant {
  void voler() {
    print('Je vole...');
  }
}

// Implémentation de l'interface
class Oiseau implements Volant {
  String nom;
  
  Oiseau(this.nom);
  
  @override
  void voler() {
    print('$nom bat des ailes et vole!');
  }
}

class Avion implements Volant {
  String modele;
  
  Avion(this.modele);
  
  @override
  void voler() {
    print('L\'avion $modele décolle avec ses réacteurs!');
  }
}

// Utilisation
void main() {
  List<Volant> objetsVolants = [
    Oiseau('Aigle'),
    Avion('Boeing 747'),
  ];
  
  for (var obj in objetsVolants) {
    obj.voler();
  }
  // Aigle bat des ailes et vole!
  // L'avion Boeing 747 décolle avec ses réacteurs!
}

🔀 Mixins

Les mixins sont un moyen de réutiliser du code dans plusieurs hiérarchies de classes. C'est une forme de composition qui permet d'ajouter des fonctionnalités à une classe sans utiliser l'héritage.

// Définition d'un mixin
mixin Nageur {
  void nager() {
    print('Je nage...');
  }
}

mixin Marcheur {
  void marcher() {
    print('Je marche...');
  }
}

// Classe de base
class Animal {
  String nom;
  Animal(this.nom);
}

// Classe qui utilise plusieurs mixins
class Canard extends Animal with Nageur, Marcheur {
  Canard(String nom) : super(nom);
  
  void voler() {
    print('$nom vole!');
  }
}

class Poisson extends Animal with Nageur {
  Poisson(String nom) : super(nom);
}

// Utilisation
void main() {
  var canard = Canard('Donald');
  canard.marcher();  // Je marche...
  canard.nager();    // Je nage...
  canard.voler();    // Donald vole!
  
  var poisson = Poisson('Nemo');
  poisson.nager();   // Je nage...
  // poisson.marcher();  // ❌ ERREUR : Poisson n'a pas le mixin Marcheur
}
Les mixins sont très utilisés dans Flutter pour ajouter des fonctionnalités aux widgets. Par exemple, SingleTickerProviderStateMixin est un mixin couramment utilisé pour les animations.

🎯 Exemple pratique : Système de bibliothèque

Mettons en pratique tous ces concepts avec un exemple complet d'un système de gestion de bibliothèque.

// Classe abstraite de base
abstract class Document {
  String titre;
  String auteur;
  int anneePublication;
  
  Document(this.titre, this.auteur, this.anneePublication);
  
  // Méthode abstraite
  void afficherInfo();
  
  // Méthode concrète
  String get description => '$titre par $auteur ($anneePublication)';
}

// Classe Livre
class Livre extends Document {
  int nombrePages;
  String isbn;
  
  Livre({
    required String titre,
    required String auteur,
    required int anneePublication,
    required this.nombrePages,
    required this.isbn,
  }) : super(titre, auteur, anneePublication);
  
  @override
  void afficherInfo() {
    print('📖 Livre: $description');
    print('   Pages: $nombrePages | ISBN: $isbn');
  }
}

// Classe Magazine
class Magazine extends Document {
  int numeroEdition;
  String periodicite;
  
  Magazine({
    required String titre,
    required String auteur,
    required int anneePublication,
    required this.numeroEdition,
    required this.periodicite,
  }) : super(titre, auteur, anneePublication);
  
  @override
  void afficherInfo() {
    print('📰 Magazine: $description');
    print('   Édition: $numeroEdition | Périodicité: $periodicite');
  }
}

// Classe Bibliothèque
class Bibliotheque {
  String nom;
  List<Document> _documents = [];
  
  Bibliotheque(this.nom);
  
  // Ajouter un document
  void ajouterDocument(Document doc) {
    _documents.add(doc);
    print('✅ Document ajouté: ${doc.titre}');
  }
  
  // Afficher tous les documents
  void afficherCatalogue() {
    print('\n📚 Catalogue de la bibliothèque "$nom"');
    print('=' * 50);
    for (var doc in _documents) {
      doc.afficherInfo();
      print('');
    }
    print('Total: ${_documents.length} document(s)');
  }
  
  // Rechercher par titre
  List<Document> rechercherParTitre(String recherche) {
    return _documents
        .where((doc) => doc.titre.toLowerCase().contains(recherche.toLowerCase()))
        .toList();
  }
}

// Programme principal
void main() {
  // Créer une bibliothèque
  var biblio = Bibliotheque('Bibliothèque Municipale');
  
  // Ajouter des documents
  biblio.ajouterDocument(Livre(
    titre: 'Clean Code',
    auteur: 'Robert C. Martin',
    anneePublication: 2008,
    nombrePages: 464,
    isbn: '978-0132350884',
  ));
  
  biblio.ajouterDocument(Livre(
    titre: 'Flutter Apprentice',
    auteur: 'raywenderlich.com Team',
    anneePublication: 2021,
    nombrePages: 723,
    isbn: '978-1950325382',
  ));
  
  biblio.ajouterDocument(Magazine(
    titre: 'Flutter Magazine',
    auteur: 'Équipe Flutter',
    anneePublication: 2024,
    numeroEdition: 12,
    periodicite: 'Mensuel',
  ));
  
  // Afficher le catalogue
  biblio.afficherCatalogue();
  
  // Rechercher
  print('\n🔍 Recherche "Flutter":');
  var resultats = biblio.rechercherParTitre('Flutter');
  for (var doc in resultats) {
    print('   - ${doc.titre}');
  }
}
Diagramme UML du système de bibliothèque
Diagramme UML du système de bibliothèque

💡 Points clés à retenir

  • Classe : Modèle définissant la structure et le comportement d'objets
  • Objet : Instance concrète d'une classe
  • Constructeur : Méthode spéciale pour créer et initialiser un objet
  • Encapsulation : Cacher les détails internes avec _ pour les membres privés
  • Getters/Setters : Contrôler l'accès aux propriétés
  • Héritage : extends pour réutiliser et étendre une classe
  • Classes abstraites : Modèles qui ne peuvent pas être instanciés directement
  • Interfaces : implements pour implémenter un contrat
  • Mixins : with pour composer des fonctionnalités
La programmation orientée objet est au cœur de Flutter. Chaque widget Flutter est une classe, et vous créerez constamment des classes pour organiser votre code. Maîtriser ces concepts est essentiel pour développer efficacement avec Flutter.

1.2.3 – Bonnes pratiques

Maintenant que vous maîtrisez les bases de Dart, il est temps d'apprendre à écrire du code de qualité. Dans cette section, nous allons découvrir les bonnes pratiques de développement en Dart, les conventions de nommage, et les techniques qui rendront votre code plus lisible, maintenable et professionnel.

📝 Conventions de nommage

Dart suit des conventions de nommage strictes qui rendent le code cohérent et facile à lire. Ces règles sont appliquées par l'outil d'analyse statique de Dart.

Classes, Enums et Types

Utilisez UpperCamelCase (chaque mot commence par une majuscule, pas d'underscore).

// ✅ BON
class Utilisateur { }
class CompteBancaire { }
class GestionnaireDeCache { }
enum StatutCommande { enAttente, validee, livree }

// ❌ MAUVAIS
class utilisateur { }
class compte_bancaire { }
class gestionnaireDeCache { }

Variables, fonctions et paramètres

Utilisez lowerCamelCase (commence par une minuscule, majuscule pour les mots suivants).

// ✅ BON
var nomUtilisateur = 'Alice';
var nombreDeConnexions = 0;
void calculerMontantTotal() { }
void envoyerEmailDeConfirmation() { }

// ❌ MAUVAIS
var NomUtilisateur = 'Alice';
var nombre_de_connexions = 0;
void CalculerMontantTotal() { }
void envoyer_email() { }

Constantes

Utilisez lowerCamelCase pour les constantes (contrairement à d'autres langages qui utilisent UPPER_CASE).

// ✅ BON
const double pi = 3.14159;
const int maxUtilisateurs = 100;
const String urlApi = 'https://api.exemple.com';

// ❌ MAUVAIS (style Java/JavaScript, pas Dart)
const double PI = 3.14159;
const int MAX_UTILISATEURS = 100;

Fichiers et bibliothèques

Utilisez snake_case (tout en minuscules avec underscores) pour les noms de fichiers.

// ✅ BON
compte_bancaire.dart
gestionnaire_utilisateur.dart
utils_date.dart

// ❌ MAUVAIS
CompteBancaire.dart
GestionnaireUtilisateur.dart
UtilsDate.dart

Propriétés privées

Préfixez avec un underscore _ pour indiquer qu'une propriété ou méthode est privée.

class CompteBancaire {
  String titulaire;     // Public
  double _solde;        // Privé
  
  CompteBancaire(this.titulaire, this._solde);
  
  double get solde => _solde;  // Getter public pour accéder au solde privé
  
  void _validerTransaction() {  // Méthode privée
    // Logique interne
  }
}
Résumé des conventions :
UpperCamelCase : Classes, Enums, Types
lowerCamelCase : Variables, fonctions, constantes
snake_case : Noms de fichiers
_private : Membres privés

🎯 Préférer const et final

Utilisez final et const autant que possible pour rendre votre code plus sûr et performant.

// ❌ MAUVAIS : Variables mutables alors qu'elles ne changent jamais
var ville = 'Paris';
var pi = 3.14159;
var maxConnexions = 100;

// ✅ BON : Variables immuables
final ville = 'Paris';
const pi = 3.14159;
const maxConnexions = 100;

// Avec des objets
// ❌ MAUVAIS
var coordonnees = Point(10, 20);

// ✅ BON
final coordonnees = Point(10, 20);  // La référence ne change pas
const origine = Point(0, 0);        // Constante de compilation
Règle simple : Utilisez const quand la valeur est connue à la compilation, final pour les valeurs qui ne changent pas mais sont calculées à l'exécution, et var uniquement pour les variables qui doivent vraiment changer.

📚 Organisation du code

Ordre des membres d'une classe

Organisez les membres d'une classe dans un ordre logique et cohérent.

class Utilisateur {
  // 1. Constantes statiques
  static const int ageMinimum = 18;
  
  // 2. Propriétés statiques
  static int nombreUtilisateurs = 0;
  
  // 3. Propriétés d'instance (publiques puis privées)
  String nom;
  String email;
  int _age;
  String? _telephone;  // Propriétés optionnelles à la fin
  
  // 4. Constructeurs
  Utilisateur(this.nom, this.email, this._age) {
    nombreUtilisateurs++;
  }
  
  // Constructeurs nommés après le principal
  Utilisateur.anonyme()
      : nom = 'Anonyme',
        email = 'anonyme@exemple.com',
        _age = 18;
  
  // 5. Getters et Setters
  int get age => _age;
  
  set age(int valeur) {
    if (valeur >= ageMinimum) {
      _age = valeur;
    }
  }
  
  // 6. Méthodes publiques
  void afficherProfil() {
    print('Utilisateur: $nom ($email)');
  }
  
  void envoyerEmail(String message) {
    print('Email envoyé à $email: $message');
  }
  
  // 7. Méthodes privées
  void _validerEmail() {
    // Logique de validation
  }
  
  // 8. Méthodes statiques
  static void afficherStatistiques() {
    print('Nombre d\'utilisateurs: $nombreUtilisateurs');
  }
  
  // 9. Méthodes override (equals, hashCode, toString)
  @override
  String toString() => 'Utilisateur($nom, $email)';
}

Imports organisés

Organisez vos imports dans cet ordre : Dart core, packages externes, fichiers locaux.

// 1. Imports Dart core
import 'dart:async';
import 'dart:io';

// 2. Imports de packages externes
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

// 3. Imports locaux (relatifs)
import '../models/utilisateur.dart';
import '../services/api_service.dart';
import 'widgets/custom_button.dart';

🔗 Les différentes manières d'importer des fichiers en Dart

En Dart, l'importation de fichiers permet d'utiliser le code provenant d'autres fichiers ou packages. Il existe plusieurs manières d'importer selon vos besoins :

  • Import standard :
    import 'package:nom_du_package/nom_du_fichier.dart';
    Utilisé pour importer des packages externes ou internes utilisant le système pub (ex : Flutter, http, etc.).
  • Import relatif :
    import '../chemin/vers/fichier.dart';
    Utile pour importer des fichiers locaux dans votre projet (remonter ou descendre dans l'arborescence).
  • Import absolu (Dart core) :
    import 'dart:math';
    Sert à importer les bibliothèques natives du langage Dart (dart:io, dart:convert, etc.).
  • Import avec alias :
    import 'package:http/http.dart' as http;
    Permet de donner un préfixe (ici http) à toutes les classes/fonctions importées pour éviter les conflits de noms ou améliorer la lisibilité.
  • Import sélectif (show/hide) :
    import 'dart:math' show pi, sqrt;
    // Importera seulement pi et sqrt
    
    import 'dart:math' hide Random;
    // Importera tout sauf Random
    Pratique pour n'exposer que certaines fonctions/classes ou en masquer d'autres.
Bonnes pratiques :
Tri : Organisez vos imports par ordre : Dart core, packages externes, imports locaux.
Alias : Utilisez toujours as lorsqu’il y a des conflits de noms.
Evitez les doublons et supprimez les imports inutilisés (l'IDE signale souvent ces erreurs).

💡 Utiliser l'inférence de type intelligemment

Dart peut inférer automatiquement les types, mais il est parfois préférable d'être explicite pour la clarté.

// ✅ BON : Inférence claire
var nom = 'Alice';              // Clairement un String
var age = 25;                   // Clairement un int
var liste = [1, 2, 3];          // Clairement une List<int>

// ✅ BON : Type explicite pour la clarté
List<Utilisateur> utilisateurs = [];  // Mieux que var pour les collections vides
Map<String, int> scores = {};         // Type explicite utile

// ❌ MAUVAIS : Inférence ambiguë
var resultat = calculComplexe();  // Quel est le type ? Pas clair

// ✅ BON : Type explicite quand c'est ambigu
double resultat = calculComplexe();

// Variables de classe : toujours typées explicitement
class Configuration {
  String apiUrl;           // ✅ BON
  int maxRetries;          // ✅ BON
  // var timeout;          // ❌ MAUVAIS dans une classe
}

🔍 Null Safety - Bonnes pratiques

Dart impose la null safety. Voici comment l'utiliser efficacement.

// ✅ BON : Utiliser ?? pour valeur par défaut
String getNom(String? nomOptional) {
  return nomOptional ?? 'Anonyme';
}

// ✅ BON : Utiliser ?. pour accès conditionnel
int? getLongueur(String? texte) {
  return texte?.length;
}

// ✅ BON : Vérifier null avec if
void afficher(String? message) {
  if (message != null) {
    // Dans ce bloc, message est automatiquement non-null
    print(message.toUpperCase());
  }
}

// ❌ MAUVAIS : Utiliser ! sans vérification
void mauvais(String? texte) {
  print(texte!.length);  // Risque d'exception si texte est null
}

// ✅ BON : N'utiliser ! que quand vous êtes CERTAIN
void bon(String? texte) {
  if (texte != null && texte.isNotEmpty) {
    // Ici, on est certain que texte n'est pas null
    traiterTexte(texte);
  }
}

// ✅ BON : late pour initialisation différée
class Service {
  late DatabaseConnection connection;
  
  Future<void> initialiser() async {
    connection = await DatabaseConnection.connect();
  }
}
Évitez au maximum l'opérateur ! (force unwrap). Il indique "je suis sûr que ce n'est pas null", mais si vous vous trompez, votre application plantera. Préférez les vérifications explicites ou les opérateurs ?? et ?..

📖 Documentation et commentaires

Écrivez des commentaires utiles et documentez votre code avec des doc comments.

Doc comments (///) pour APIs publiques

/// Calcule le montant total TTC à partir d'un prix HT.
///
/// Le [prixHT] doit être un nombre positif.
/// Le [tauxTVA] est un nombre décimal (ex: 0.20 pour 20%).
///
/// Retourne le prix TTC calculé.
///
/// Exemple:
/// ```dart
/// double prixTTC = calculerPrixTTC(100, 0.20);  // 120.0
/// ```
double calculerPrixTTC(double prixHT, double tauxTVA) {
  return prixHT * (1 + tauxTVA);
}

/// Représente un utilisateur de l'application.
///
/// Chaque utilisateur a un [nom], un [email] et un [age].
/// L'email doit être unique dans le système.
class Utilisateur {
  /// Le nom complet de l'utilisateur.
  final String nom;
  
  /// L'adresse email (doit être valide et unique).
  final String email;
  
  /// L'âge de l'utilisateur en années.
  final int age;
  
  /// Crée un nouvel utilisateur.
  Utilisateur(this.nom, this.email, this.age);
}

Commentaires simples pour la logique

void traiterCommande(Commande commande) {
  // Vérifier si la commande est valide
  if (!commande.estValide) {
    throw ArgumentError('Commande invalide');
  }
  
  // Calculer le montant total avec les remises
  final montantBase = commande.calculerMontantBase();
  final remise = commande.calculerRemise();
  final montantFinal = montantBase - remise;
  
  // TODO: Implémenter le traitement du paiement
  
  // FIXME: Bug potentiel avec les remises cumulées
  
  // Enregistrer la commande
  _sauvegarderCommande(commande, montantFinal);
}
Utilisez les commentaires TODO et FIXME :
// TODO: Pour marquer du code à compléter plus tard
// FIXME: Pour indiquer un bug connu à corriger
// NOTE: Pour des remarques importantes

La plupart des IDE détectent ces mots-clés et les mettent en évidence.

⚡ Performance et optimisation

Éviter les calculs répétés

// ❌ MAUVAIS : Calcul répété dans une boucle
for (var i = 0; i < liste.length; i++) {  // liste.length calculé à chaque itération
  print(liste[i]);
}

// ✅ BON : Utiliser for-in
for (var element in liste) {
  print(element);
}

// ✅ BON : Stocker la longueur si nécessaire
final longueur = liste.length;
for (var i = 0; i < longueur; i++) {
  print(liste[i]);
}

Utiliser const pour les widgets Flutter

// ❌ MAUVAIS : Widget recréé à chaque rebuild
Widget build(BuildContext context) {
  return Column(
    children: [
      Text('Titre fixe'),
      SizedBox(height: 20),
    ],
  );
}

// ✅ BON : Widgets const ne sont pas recréés
Widget build(BuildContext context) {
  return Column(
    children: const [
      Text('Titre fixe'),
      SizedBox(height: 20),
    ],
  );
}

Préférer les collections literals

// ❌ MAUVAIS : Constructeurs
var liste = List<int>();
var map = Map<String, int>();
var set = Set<String>();

// ✅ BON : Literals (plus court et plus rapide)
var liste = <int>[];
var map = <String, int>{};
var set = <String>{};

// Avec valeurs
var nombres = [1, 2, 3];
var ages = {'Alice': 25, 'Bob': 30};
var langages = {'Dart', 'Flutter', 'JavaScript'};

🧪 Gestion des erreurs

// ✅ BON : Try-catch spécifique
Future<void> chargerDonnees() async {
  try {
    final data = await api.fetchData();
    traiterDonnees(data);
  } on NetworkException catch (e) {
    // Gérer les erreurs réseau spécifiquement
    print('Erreur réseau: ${e.message}');
    afficherMessageHorsLigne();
  } on FormatException catch (e) {
    // Gérer les erreurs de format
    print('Données invalides: $e');
  } catch (e, stackTrace) {
    // Attraper toutes les autres erreurs
    print('Erreur inattendue: $e');
    print('Stack trace: $stackTrace');
    rethrow;  // Relancer si on ne peut pas gérer
  }
}

// ✅ BON : Validation avec exceptions claires
double diviser(double a, double b) {
  if (b == 0) {
    throw ArgumentError('Division par zéro impossible');
  }
  return a / b;
}

// ✅ BON : Finally pour le nettoyage
Future<void> traiterFichier(String path) async {
  File? file;
  try {
    file = File(path);
    final content = await file.readAsString();
    traiterContenu(content);
  } catch (e) {
    print('Erreur de lecture: $e');
  } finally {
    // Exécuté dans tous les cas (erreur ou non)
    await file?.close();
  }
}

🎨 Lisibilité du code

Fonctions courtes et ciblées

// ❌ MAUVAIS : Fonction trop longue qui fait trop de choses
void traiterUtilisateur(Map<String, dynamic> data) {
  // Validation
  if (data['nom'] == null) throw ArgumentError('Nom manquant');
  if (data['email'] == null) throw ArgumentError('Email manquant');
  // Création
  final user = Utilisateur(data['nom'], data['email']);
  // Sauvegarde
  database.save(user);
  // Notification
  emailService.sendWelcome(user.email);
  // Logging
  logger.log('Utilisateur créé: ${user.nom}');
}

// ✅ BON : Fonctions courtes et spécifiques
void traiterUtilisateur(Map<String, dynamic> data) {
  _validerDonnees(data);
  final user = _creerUtilisateur(data);
  _sauvegarderUtilisateur(user);
  _envoyerEmailBienvenue(user);
  _loggerCreation(user);
}

void _validerDonnees(Map<String, dynamic> data) {
  if (data['nom'] == null) throw ArgumentError('Nom manquant');
  if (data['email'] == null) throw ArgumentError('Email manquant');
}

Utilisateur _creerUtilisateur(Map<String, dynamic> data) {
  return Utilisateur(data['nom'], data['email']);
}

// ... autres fonctions privées

Noms de variables descriptifs

// ❌ MAUVAIS : Noms courts et ambigus
var d = 86400;
var t = DateTime.now();
var l = users.length;

// ✅ BON : Noms descriptifs
var secondesParJour = 86400;
var maintenant = DateTime.now();
var nombreUtilisateurs = users.length;

// Exception : variables de boucle
for (var i = 0; i < liste.length; i++) {  // i est acceptable
  // ...
}

// Mais si imbriqué, soyez plus explicite
for (var ligne = 0; ligne < grille.length; ligne++) {
  for (var colonne = 0; colonne < grille[ligne].length; colonne++) {
    // ...
  }
}

✅ Checklist des bonnes pratiques

Avant de commiter votre code, vérifiez :

✓ Les conventions de nommage sont respectées
✓ J'ai utilisé final ou const quand possible
✓ Pas d'utilisation abusive de ! (force unwrap)
✓ Les classes publiques ont des doc comments
✓ Les imports sont organisés
✓ Pas de code mort (commenté ou inutilisé)
✓ Les fonctions font une seule chose
✓ Les noms de variables sont clairs
✓ Les erreurs sont gérées correctement
✓ Le code passe l'analyse statique (dart analyze)

🛠️ Outils pour maintenir la qualité

Analyse statique

# Analyser votre code
dart analyze

# Formater automatiquement votre code
dart format .

# Vérifier le code Flutter
flutter analyze

Fichier analysis_options.yaml

Configurez les règles d'analyse dans votre projet.

# analysis_options.yaml
include: package:flutter_lints/flutter.yaml

linter:
  rules:
    # Conventions de nommage
    - camel_case_types
    - file_names
    - non_constant_identifier_names
    
    # Null safety
    - avoid_null_checks_in_equality_operators
    - prefer_is_not_empty
    
    # Style
    - prefer_const_constructors
    - prefer_final_fields
    - prefer_final_locals
    
    # Performance
    - unnecessary_null_in_if_null_operators
    
    # Documentation
    - public_member_api_docs

💡 Points clés à retenir

  • Conventions : UpperCamelCase (classes), lowerCamelCase (variables/méthodes), snake_case (fichiers)
  • Immutabilité : Préférer const et final à var
  • Null safety : Utiliser ?? et ?., éviter !
  • Documentation : Doc comments (///) pour les APIs publiques
  • Organisation : Ordre logique des membres, imports groupés
  • Lisibilité : Fonctions courtes, noms descriptifs
  • Outils : dart analyze et dart format
Ces bonnes pratiques ne sont pas que des règles esthétiques. Elles améliorent la maintenabilité, réduisent les bugs et facilitent le travail en équipe. Prenez l'habitude de les appliquer dès maintenant, elles deviendront naturelles avec la pratique.
Pour aller plus loin :
Consultez le guide officiel des bonnes pratiques Dart :
dart.dev/guides/language/effective-dart

Il couvre en détail le style, la documentation, l'usage et le design du code Dart.

1.2.4 – Installation et premiers programmes

Pour développer en Dart et Flutter, vous avez le choix entre plusieurs éditeurs de code. Dans cette section, nous allons découvrir Visual Studio Code (VSCode), un éditeur de code léger, rapide et très populaire dans la communauté Flutter. VSCode est une excellente option pour débuter en Dart et créer vos premiers programmes. Nous verrons comment le configurer et créer votre premier programme "Hello World". (Note : nous découvrirons également Android Studio dans la section 1.3.1, qui est un autre excellent choix pour le développement Flutter.)

💻 Pourquoi choisir VSCode ?

VSCode offre plusieurs avantages pour le développement Flutter et Dart :

  • Léger et rapide : Démarrage beaucoup plus rapide qu'Android Studio
  • Gratuit et open source : Développé par Microsoft
  • Extensible : Des milliers d'extensions disponibles
  • Multi-plateforme : Fonctionne sur Windows, macOS et Linux
  • Intégration Git : Gestion de version intégrée
  • Terminal intégré : Exécuter des commandes sans quitter l'éditeur
VSCode est particulièrement adapté si vous débutez en programmation ou si vous préférez un environnement plus simple et rapide. Android Studio reste excellent pour les projets complexes et le débogage avancé, mais VSCode est parfait pour apprendre et développer rapidement.

📥 Étape 1 : Installation de Visual Studio Code

Commençons par installer VSCode sur votre ordinateur Windows.

Téléchargement

  1. Rendez-vous sur code.visualstudio.com
  2. Cliquez sur le bouton "Download for Windows"
  3. Le fichier d'installation (environ 100 Mo) se télécharge automatiquement

Installation

  1. Double-cliquez sur le fichier téléchargé (VSCodeUserSetup-x64-xxx.exe)
  2. L'assistant d'installation s'ouvre
  3. Options recommandées à cocher :
    • ☑ Ajouter à PATH (permet d'ouvrir VSCode depuis le terminal)
    • ☑ Créer une entrée dans le menu contextuel "Ouvrir avec Code"
    • ☑ Enregistrer les fichiers .code associés à VSCode
  4. Cliquez sur "Suivant" puis "Installer"
  5. L'installation prend quelques secondes
  6. Cliquez sur "Terminer" pour lancer VSCode
Si vous avez déjà installé Flutter (comme vu dans la section 1.3.1), vous pouvez utiliser VSCode immédiatement. Sinon, assurez-vous d'avoir installé Flutter et Dart avant de continuer.

🔌 Étape 2 : Installation de l'extension Flutter

VSCode fonctionne avec des extensions qui ajoutent des fonctionnalités. Pour développer en Flutter et Dart, nous devons installer l'extension officielle Flutter. Vous pouvez également la trouver directement sur le Marketplace VSCode.

Méthode 1 : Via le panneau Extensions

  1. Ouvrez VSCode
  2. Cliquez sur l'icône Extensions dans la barre latérale gauche (ou appuyez sur Ctrl+Shift+X)
  3. Dans la barre de recherche, tapez : Flutter
  4. Recherchez "Flutter" par Dart Code (publié par flutter.dev)
  5. Cliquez sur "Installer"
  6. L'extension Dart sera automatiquement installée en même temps (c'est une dépendance)
Install Flutter in VSCode
Install Flutter in VSCode

Méthode 2 : Via la commande

  1. Appuyez sur Ctrl+Shift+P pour ouvrir la palette de commandes
  2. Tapez : Extensions: Install Extensions
  3. Recherchez "Flutter" et installez

Vérification de l'installation

Après l'installation, vous devriez voir :

  • Un message de notification indiquant que l'extension est installée
  • VSCode peut demander de redémarrer (cliquez sur "Reload" si demandé)
  • L'extension apparaît dans la liste des extensions installées (panneau Extensions)
Note importante : L'icône Flutter dans la barre latérale gauche n'apparaît que lorsque vous ouvrez un projet Flutter (un dossier contenant un fichier pubspec.yaml). Pour l'instant, vous pouvez vérifier que l'extension est installée en allant dans le panneau Extensions et en cherchant "Flutter" dans les extensions installées.
L'extension Flutter installe automatiquement l'extension Dart. Les deux fonctionnent ensemble pour vous offrir :
  • Coloration syntaxique Dart
  • Autocomplétion intelligente
  • Débogage intégré
  • Hot Reload depuis VSCode
  • Détection automatique des appareils Flutter

⚙️ Étape 3 : Configuration de VSCode pour Flutter

Une fois l'extension installée, vérifions que VSCode détecte bien Flutter.

Vérifier le chemin Flutter

  1. Appuyez sur Ctrl+Shift+P pour ouvrir la palette de commandes
  2. Tapez : Dart: Change SDK
  3. Si Flutter est dans votre PATH, VSCode le détectera automatiquement
  4. Sinon, indiquez le chemin : C:\dev\flutter (ou votre emplacement)

Vérifier la configuration

Dans le terminal intégré de VSCode (Terminal → Nouveau terminal ou Ctrl+`), tapez :

# Vérifier que Flutter est détecté
flutter --version

# Diagnostic complet
flutter doctor
Si flutter n'est pas reconnu dans le terminal de VSCode, vérifiez que vous avez bien ajouté Flutter au PATH système (voir section 1.3.1). Vous devrez peut-être redémarrer VSCode après avoir modifié le PATH.

📝 Étape 4 : Créer votre premier programme Dart

Maintenant que tout est configuré, créons votre premier programme Dart : un classique "Hello World" !

Créer un nouveau fichier

  1. Dans VSCode, cliquez sur Fichier → Nouveau fichier (ou Ctrl+N)
  2. Enregistrez le fichier avec Ctrl+S
  3. Nommez-le : hello_world.dart
  4. Choisissez un emplacement (ex: C:\dev\dart_programs)
  5. Cliquez sur "Enregistrer"
Conseil d'organisation : Créez un dossier dédié pour vos programmes Dart d'apprentissage, par exemple C:\dev\dart_programs. Cela vous aidera à organiser vos fichiers.

Écrire le code "Hello World"

Dans le fichier hello_world.dart, tapez le code suivant :

// Mon premier programme Dart
void main() {
  print('Hello, World!');
  print('Bienvenue dans le monde de Dart !');
}

Explication du code

Analysons ce code ligne par ligne :

  • Ligne 1 : // Mon premier programme Dart - C'est un commentaire. Tout ce qui suit // sur une ligne est ignoré par Dart. Les commentaires servent à documenter votre code.
  • Ligne 2 : void main() { - Définit la fonction main(), qui est le point d'entrée de tout programme Dart. void signifie que cette fonction ne retourne rien.
  • Ligne 3 : print('Hello, World!'); - Affiche le texte "Hello, World!" dans la console. print() est une fonction intégrée à Dart.
  • Ligne 4 : print('Bienvenue dans le monde de Dart !'); - Affiche un second message.
  • Ligne 5 : } - Ferme le bloc de la fonction main().
Structure d'un programme Dart simple :

void main() {
  // Votre code ici
}


Tous les programmes Dart commencent par la fonction main(). C'est là que l'exécution commence.

▶️ Étape 5 : Exécuter le programme

Maintenant, exécutons notre programme pour voir le résultat !

Méthode 1 : Via le terminal intégré de VSCode

  1. Ouvrez le terminal intégré : Terminal → Nouveau terminal (ou Ctrl+`)
  2. Assurez-vous d'être dans le bon dossier. Si nécessaire, naviguez :
    cd C:\dev\dart_programs
  3. Exécutez le programme avec la commande :
    dart run hello_world.dart

Vous devriez voir dans le terminal :

Hello, World!
Bienvenue dans le monde de Dart !
Capture d'écran du terminal VSCode affichant le résultat de 'dart run hello_world.dart'
Terminal VSCode avec le résultat de l'exécution

En haut : Barre de commande avec dart run hello_world.dart
Résultat affiché :
Hello, World!
Bienvenue dans le monde de Dart !

En bas : Prompt du terminal prêt pour la prochaine commande

Méthode 2 : Via le bouton "Run" de VSCode

  1. Ouvrez votre fichier hello_world.dart
  2. Un bouton Run ▷ (ou Run and Debug) apparaît en haut à droite
  3. Cliquez dessus
  4. Le résultat s’affiche dans Debug Console ou Terminal
Exécution du programme Dart dans le terminal
Exécution d’un programme Dart

Exemple du terminal affichant le résultat via le bouton « Run » de VSCode.
Raccourci clavier : Vous pouvez aussi exécuter le code avec Ctrl+F5 (Run without debugging) ou F5 (Run with debugging).

Variante : Programme interactif

Créons une version plus interactive de notre programme :

import 'dart:io';

void main() {
  print('=== Bienvenue dans Dart ===');
  print('');
  
  // Demander le nom de l'utilisateur
  stdout.write('Quel est votre nom ? ');
  String? nom = stdin.readLineSync();
  
  // Afficher un message personnalisé
  if (nom != null && nom.isNotEmpty) {
    print('Bonjour, $nom ! Bienvenue dans le monde de Dart !');
  } else {
    print('Bonjour, visiteur anonyme !');
  }
  
  print('');
  print('Programme terminé. À bientôt !');
}

Ce programme :

  • Demande votre nom à l'utilisateur
  • Lit la réponse depuis le terminal
  • Affiche un message personnalisé

Exécutez-le avec dart run et testez-le !

Import dart:io : Pour lire depuis le terminal, nous devons importer la bibliothèque dart:io. C'est nécessaire pour stdin.readLineSync() et stdout.write().

🎨 Fonctionnalités utiles de VSCode pour Dart

VSCode offre de nombreuses fonctionnalités qui facilitent le développement Dart :

Autocomplétion intelligente

Tapez quelques lettres et VSCode vous propose des suggestions. Appuyez sur Tab ou Entrée pour accepter.

Coloration syntaxique

Le code est automatiquement coloré pour améliorer la lisibilité : mots-clés en bleu, chaînes en vert, commentaires en gris, etc.

Détection d'erreurs en temps réel

VSCode souligne les erreurs en rouge avant même d'exécuter le code. Passez la souris pour voir le message d'erreur.

Formatage automatique

Formatez votre code automatiquement :

  • Raccourci : Shift+Alt+F (Windows) ou Shift+Option+F (Mac)
  • Menu : Clic droit → "Format Document"

Navigation rapide

  • Ctrl+P : Ouvrir rapidement un fichier
  • Ctrl+Shift+F : Rechercher dans tous les fichiers
  • F12 : Aller à la définition d'une fonction/classe
  • Alt+F12 : Voir la définition sans quitter le fichier

🌐 Étape 6 : Tester Dart en ligne (sans installation)

Si vous n'avez pas encore installé Flutter ou Dart, ou si vous voulez tester rapidement du code, vous pouvez utiliser des outils en ligne gratuits !

DartPad - L'outil officiel en ligne

DartPad est l'éditeur Dart en ligne officiel créé par l'équipe Dart. C'est parfait pour :

  • Tester rapidement du code Dart
  • Partager des exemples de code
  • Apprendre sans installer quoi que ce soit
  • Expérimenter avec les fonctionnalités de Dart

Comment utiliser DartPad

  1. Rendez-vous sur dartpad.dev
  2. Vous voyez un éditeur de code dans votre navigateur
  3. Tapez ou collez votre code Dart
  4. Cliquez sur le bouton "Run" (ou appuyez sur Ctrl+Enter)
  5. Le résultat s'affiche dans le panneau de droite
Interface de DartPad
Interface de DartPad

À gauche : Éditeur de code avec le code Dart
• Bouton "Run" en haut
• Bouton "Format" pour formater le code
• Bouton "Reset" pour réinitialiser
À droite : Panneau de sortie
• Affiche le résultat de print()
• Affiche les erreurs s'il y en a
En bas : Options (null safety, SDK version)

Exemple avec DartPad

Essayez ce code dans DartPad :

void main() {
  print('Hello depuis DartPad !');
  
  // Calcul simple
  int a = 10;
  int b = 20;
  int somme = a + b;
  
  print('La somme de $a et $b est $somme');
  
  // Boucle
  for (int i = 1; i <= 5; i++) {
    print('Compteur: $i');
  }
}

Cliquez sur "Run" et voyez le résultat instantanément !

Exemple avec DartPad - capture d'écran
Exemple d’exécution dans DartPad

À gauche : code Dart tapé dans l’éditeur.
À droite : panneau de sortie affichant le résultat du programme après avoir cliqué sur « Run ».
Avantages de DartPad :

✅ Aucune installation requise
✅ Fonctionne sur n'importe quel ordinateur avec un navigateur
✅ Partage facile (lien URL)
✅ Toujours à jour avec la dernière version de Dart
✅ Parfait pour les tutoriels et démonstrations

Limitations :
❌ Pas d'accès aux fichiers système
❌ Pas de packages externes (sauf quelques-uns préchargés)
❌ Pas de débogage avancé

Autres outils en ligne

Outre DartPad, vous pouvez aussi utiliser :

  • Replit (replit.com) : Environnement de développement complet en ligne, supporte Dart
  • CodePen (codepen.io) : Principalement pour le web, mais peut être utilisé pour du code Dart simple
  • GitHub Codespaces (github.com/codespaces) : Environnement de développement dans le cloud (nécessite un compte GitHub)
Quand utiliser quoi ?

DartPad : Pour tester rapidement, apprendre, partager des exemples
VSCode : Pour développer des programmes complets, projets locaux
Android Studio : Pour développer des applications Flutter complètes

Pour débuter, DartPad est excellent. Passez à VSCode quand vous voulez créer des projets plus complexes.

💡 Points clés à retenir

  • VSCode : Éditeur léger et rapide, excellent pour apprendre Dart
  • Extension Flutter : Nécessaire pour le support Dart/Flutter dans VSCode
  • Structure d'un programme Dart : Commence toujours par void main() { }
  • print() : Fonction pour afficher du texte dans la console
  • dart run : Commande pour exécuter un fichier Dart
  • DartPad : Outil en ligne gratuit pour tester Dart sans installation
  • Autocomplétion : VSCode suggère automatiquement du code
  • Formatage : Shift+Alt+F pour formater le code automatiquement
Prochaines étapes :

Maintenant que vous savez créer et exécuter des programmes Dart simples, vous êtes prêt à :
  • Explorer les types de données (section 1.2.1)
  • Créer des fonctions plus complexes
  • Découvrir les classes et l'OOP (section 1.2.2)
  • Passer à Flutter pour créer des interfaces graphiques (chapitre suivant)
Conseil pratique :

Créez un dossier mes_programmes_dart et gardez tous vos fichiers d'apprentissage organisés. Chaque nouveau concept que vous apprenez, créez un fichier pour le tester. Par exemple :
  • hello_world.dart
  • test_variables.dart
  • test_fonctions.dart
  • test_classes.dart
Cela vous aidera à progresser de manière structurée !