Dart : l'essentiel pour Flutter

Première leçon du cours Applications mobiles avec Flutter, en accès libre : environ 18 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Déclarer des variables typées avec var, final et const et utiliser la sûreté vis-à-vis de null
  • Écrire des fonctions avec paramètres nommés et des classes avec constructeurs
  • Manipuler listes et maps et écrire du code asynchrone avec Future, async et await

Variables et typesSûreté vis-à-vis de nullFonctions et classesFuture, async et await

Pourquoi Dart

Flutter, le framework de Google pour créer des applications iOS, Android, web et bureau à partir d’un seul code, s’écrit en Dart. Dart ressemble à JavaScript, Java ou C# : si vous connaissez les bases de la programmation (variables, conditions, boucles, fonctions), vous le lirez vite.

Pour essayer sans rien installer, utilisez DartPad (dartpad.dev) dans le navigateur. Pour développer, installez le SDK Flutter (il inclut Dart) et vérifiez l’installation avec flutter doctor.

void main() {
  print('Bonjour Flutter');
}

Tout programme commence par la fonction main. Les instructions se terminent par ;.

Variables et types

var ville = 'Lyon';          // type inféré : String
int quantite = 3;
double prix = 19.9;
bool enStock = true;
final dateCommande = DateTime.now();   // ne peut plus être réaffectée
const tauxTva = 0.2;                   // connue à la compilation
  • var : le type est déduit de la valeur et ne change plus (ville = 3 est une erreur).
  • final : la variable reçoit une valeur une seule fois, éventuellement calculée à l’exécution.
  • const : la valeur est connue à la compilation ; Flutter s’en sert pour réutiliser des widgets identiques.

Les chaînes s’écrivent entre apostrophes ou guillemets et s’interpolent : 'Total : $quantite articles', 'TTC : ${prix * 1.2}'.

La sûreté vis-à-vis de null

Par défaut, une variable ne peut pas valoir null. Pour l’autoriser, on ajoute ? au type :

String nom = 'Léa';      // jamais null
String? telephone;       // peut être null (vaut null ici)

print(telephone?.length);        // null : ?. n'appelle rien si la valeur est null
print(telephone ?? 'non renseigné');   // ?? fournit une valeur de repli
int longueur = telephone!.length;      // ! : « je garantis que ce n'est pas null » — plante sinon

Le compilateur refuse telephone.length sans précaution : la plupart des erreurs « null » sont détectées avant l’exécution. Évitez ! sauf quand vous êtes certain de la valeur ; une vérification if (telephone != null) permet ensuite d’utiliser telephone comme un String.

Fonctions et paramètres nommés

double prixTtc(double ht, {double taux = 0.2}) => ht * (1 + taux);

String etiquette({required String nom, required double prix, bool promo = false}) {
  return promo ? '$nom (promo) : ${prix.toStringAsFixed(2)} €' : '$nom : ${prix.toStringAsFixed(2)} €';
}

void main() {
  print(prixTtc(100));                         // 120.0
  print(etiquette(nom: 'Casque', prix: 39));   // Casque : 39.00 €
}

Les paramètres nommés, entre accolades, s’appellent par leur nom ; required les rend obligatoires. Flutter les utilise partout : Text('Bonjour', style: …), Padding(padding: …, child: …). La flèche => abrège une fonction qui renvoie une seule expression.

Classes

class Produit {
  final String nom;
  final double prix;
  final int stock;

  const Produit({required this.nom, required this.prix, this.stock = 0});

  bool get disponible => stock > 0;          // accesseur calculé

  @override
  String toString() => 'Produit($nom, $prix)';
}

final casque = Produit(nom: 'Casque', prix: 39, stock: 4);
print(casque.disponible);   // true

this.nom dans les paramètres du constructeur initialise directement le champ. Des champs final et un constructeur const rendent l’objet immuable, ce qui est la norme pour les données en Flutter.

Collections

final produits = [
  Produit(nom: 'Casque', prix: 39, stock: 4),
  Produit(nom: 'Clavier', prix: 59),
];
final noms = produits.where((p) => p.disponible).map((p) => p.nom).toList();   // [Casque]
final stocks = <String, int>{'Casque': 4, 'Clavier': 0};                       // Map
print(stocks['Casque']);   // 4 ; une clé absente donne null

where filtre, map transforme ; tous deux renvoient un Iterable paresseux, que toList() convertit en liste.

L’asynchrone : Future, async, await

Une opération longue (réseau, fichier) renvoie un Future : une valeur disponible plus tard.

Future<String> chargerClient(int id) async {
  await Future.delayed(const Duration(milliseconds: 300));   // simule le réseau
  if (id <= 0) throw ArgumentError('Identifiant invalide');
  return 'Client $id';
}

Future<void> main() async {
  try {
    print(await chargerClient(7));   // Client 7
    await chargerClient(0);
  } catch (e) {
    print('Erreur : $e');
  }
}

await suspend la fonction async sans bloquer l’interface, et try/catch capture les erreurs. Vous retrouverez exactement ce modèle pour les appels d’API.

À retenir

  • var, final, const ; types non nuls par défaut, ?, ?., ??, ! avec prudence.
  • Paramètres nommés {required …}, classes immuables avec final et const.
  • where/map/toList, Future + async/await + try/catch.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. TâcheDifficulté 12 indices

    Produits disponibles et total TTC

    Dans DartPad (ou avec dart run en local), écrivez une classe Produit avec les champs final String nom, final double prix et final int? stock, un constructeur const Produit(this.nom, this.prix, {this.stock}) et une méthode double prixTtc([double taux = 0.2]).

    Dans main, créez cette liste :

    nom,prix,stock
    Casque,40,3
    Clavier,50,(aucun)
    Souris,25,0

    Puis affichez, avec deux print :

    1. la liste des noms des produits dont le stock est strictement positif (un stock absent compte pour 0) ;
    2. le total TTC des trois produits avec toStringAsFixed(2).
    • Le programme compile sans avertissement de null safety
    • Le stock absent est traité avec ?? 0 et non avec !
    • La sortie comporte exactement deux lignes
  2. TâcheDifficulté 22 indices

    Ordre d'exécution asynchrone

    Exécutez ce programme dans DartPad, puis complétez la fonction chargerNom pour qu’elle attende 100 ms, lève Exception('introuvable') si id == 0 et renvoie 'Client $id' sinon.

    Future<String> chargerNom(int id) async {
      // à compléter
    }
    
    Future<void> main() async {
      print('Début');
      final nom = await chargerNom(7);
      print(nom);
      try {
        await chargerNom(0);
      } catch (e) {
        print('Erreur : $e');
      }
      print('Fin');
    }
    • chargerNom utilise await Future.delayed(const Duration(milliseconds: 100))
    • L'erreur est levée avec throw Exception('introuvable')
    • La sortie comporte quatre lignes dans l'ordre attendu
  3. QCMDifficulté 11 indice

    Opérateurs de null safety

    Que affiche ce code ?

    String? ville;
    print(ville?.length ?? 0);
    • null
    • 0
    • Une erreur à l'exécution
    • Une erreur de compilation

Quiz de fin de leçon

  1. Quelle déclaration accepte la valeur null ?

    • String nom;
    • String? nom;
    • final String nom = '';
  2. Comment rend-on un paramètre nommé obligatoire ?

    • Avec le mot-clé required
    • Avec !
    • En le plaçant en premier
  3. Que renvoie une fonction marquée async qui renvoie un int ?

    • int
    • Future<int>
    • Stream<int>

La suite du cours

  1. Widgets et mise en page18 min
  2. État et interactions20 min
  3. Réseau et données JSON18 min
  4. Préparer la publication d'une application Flutter16 min

Applications mobiles avec Flutter · Intermédiaire · 1 h 30