Aller au contenu
⬅️ Retour au sommaire ⬅️ Retour au Chapitre 1

✅ Corrigés de la première journée

Exercice : Checklist d’environnement

Vérifiez chaque point avant de continuer :

Problème fréquent : flutter devices ne voit pas le téléphone Android → vérifiez que le débogage USB est actif ET que vous avez accepté le popup d’autorisation sur l’écran du téléphone lui-même (il apparaît uniquement au premier branchement).


Exercice 5.1 — Dart pur

// dart_exercices.dart
// Exécution : dart run dart_exercices.dart

class Livre {
  final String titre;
  final String auteur;
  final int anneePublication;

  Livre(this.titre, this.auteur, this.anneePublication);

  String resume() => '"$titre" par $auteur ($anneePublication)';
}

bool estPair(int n) => n % 2 == 0;

void main() {
  // 1. Fonction estPair
  print("4 est pair : ${estPair(4)}"); // true
  print("7 est pair : ${estPair(7)}"); // false

  // 2. liste de livres
  final livres = [
    Livre("1984", "George Orwell", 1949),
    Livre("Dune", "Frank Herbert", 1965),
    Livre("Sapiens", "Yuval Noah Harari", 2011),
    Livre("Fondation", "Isaac Asimov", 1951),
    Livre("Clean Code", "Robert C. Martin", 2008),
  ];

  // 3. Filtrer > 2000, trier par année croissante
  final livresRecents = livres
      .where((l) => l.anneePublication > 2000)
      .toList()
    ..sort((a, b) => a.anneePublication.compareTo(b.anneePublication));

  print("\nLivres publiés après 2000, triés par année :");
  for (final livre in livresRecents) {
    print("- ${livre.resume()}");
  }
}

Sortie attendue :

4 est pair : true
7 est pair : false

Livres publiés après 2000, triés par année :
- "Clean Code" par Robert C. Martin (2008)
- "Sapiens" par Yuval Noah Harari (2011)

Exercice 5.2 — Compteur amélioré

Rendu possible du code ci-dessous :

représentation graphique du fonctionnement du compteur amélioré

import 'package:flutter/material.dart';

void main() => runApp(const MonApplication());

class MonApplication extends StatelessWidget {
  const MonApplication({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Compteur',
      home: const PageCompteur(),
    );
  }
}

class PageCompteur extends StatefulWidget {
  const PageCompteur({super.key});

  @override
  State<PageCompteur> createState() => _PageCompteurState();
}

class _PageCompteurState extends State<PageCompteur> {
  int _compteur = 0;

  void _incrementer() {
    setState(() => _compteur++);
  }

  void _decrementer() {
    setState(() {
      // 3. Empêcher le compteur de devenir négatif
      if (_compteur > 0) _compteur--;
    });
  }

  void _reinitialiser() {
    setState(() => _compteur = 0);
  }

  @override
  Widget build(BuildContext context) {
    // 2. Couleur rouge si > 10
    final couleur = _compteur > 10 ? Colors.red : Colors.black;

    return Scaffold(
      appBar: AppBar(title: const Text("Compteur amélioré")),
      body: Center(
        child: Text(
          "Valeur : $_compteur",
          style: TextStyle(fontSize: 32, color: couleur, fontWeight: FontWeight.bold),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          FloatingActionButton(
            heroTag: "decrementer",
            onPressed: _decrementer,
            child: const Icon(Icons.remove),
          ),
          FloatingActionButton(
            heroTag: "reinitialiser",
            backgroundColor: Colors.grey,
            onPressed: _reinitialiser,
            child: const Icon(Icons.refresh),
          ),
          FloatingActionButton(
            heroTag: "incrementer",
            onPressed: _incrementer,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Une autre version avec des couleurs différentes et quelques explications :

import 'package:flutter/material.dart';

//Version un peu différente de ce qui est proposé dans le cours 
//pour créer votre projet Flutter : flutter create compteur_ameliore
// rameque dans les appels de fonctions dans les onPressed des FloatingActionButton, on utilise des fonctions anonymes (ou fléchées) pour appeler les méthodes de l'état (_incrementer, _decrementer, _reinitialiser).
// particulaarité, on ne met pas de parenthèses après le nom de la fonction, car on ne veut pas l'exécuter immédiatement, mais seulement lorsqu'on appuie sur le bouton.
// fonction main() qui lance l'application (version fléchée)
void main() => runApp(const MonApplication());

// application principale de l'application
class MonApplication extends StatelessWidget {
  const MonApplication({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Compteur',
      home: const PageCompteur(),
    );
  }
}

// page principale de l'application
class PageCompteur extends StatefulWidget {
  // constructeur de la page principale
  const PageCompteur({super.key});

  @override
  State<PageCompteur> createState() => _PageCompteurState();
}

class _PageCompteurState extends State<PageCompteur> {
  int _compteur = 0;

  void _incrementer() {
    setState(() => _compteur++);
  }

  void _decrementer() {
    setState(() {
      // on empêche le compteur d'avoir une valeur négative !
      if (_compteur > 0) _compteur--;
    });
  }

  void _reinitialiser() {
    setState(() => _compteur = 0);
  }

  @override
  Widget build(BuildContext context) {
    // Définir une variable couleur de type final (Couleur rouge si > 10)
    final couleur = _compteur > 10 ? Colors.red : Colors.black;

    return Scaffold(
      appBar: AppBar(title: const Text("Compteur amélioré")),
      body: Center(
        child: Text(
          "Valeur : $_compteur",
          style: TextStyle(fontSize: 32, color: couleur, fontWeight: FontWeight.bold),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          FloatingActionButton(
            heroTag: "decrementer",
            onPressed: _decrementer,
            backgroundColor: Colors.yellow,
            child: const Icon(Icons.remove),
          ),
          FloatingActionButton(
            heroTag: "reinitialiser",
            backgroundColor: Colors.grey,
            onPressed: _reinitialiser,
            child: const Icon(Icons.refresh),
          ),
          FloatingActionButton(
            heroTag: "incrementer",
            onPressed: _incrementer,
            backgroundColor: Colors.orange,
            focusColor: Colors.lightGreenAccent,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Point important : chaque FloatingActionButton a un heroTag différent. Sans cela, Flutter lève une exception car 2 widgets Hero (utilisés en interne pour l’animation) ne peuvent pas partager la même clé sur un même écran ! Cela ne vous posera pas de problème avec un seul écran (Widget) mais le sera avec des navigations depuis d’autres écrans sauf s’il n’y a qu’un bouton dans la page.


Exercice 5.3 — Widget CarteInfo

Plus simple à réaliser, voici pour vous aider, il vous reste à coder ;)

représentation graphique de d'un Widget CarteInfo à créer

import 'package:flutter/material.dart';

void main() => runApp(const MonApp());

class MonApp extends StatelessWidget {
  const MonApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(title: const Text('Mes informations personnelles')),
        body: ListView(
          children: const [
            CarteInfo(
              icone: Icons.school,
              titre: 'Formation',
              description: '5 jours de cours Flutter intensif',
            ),
          ],
        ),
      ),
    );
  }
}

class CarteInfo extends StatelessWidget {
  final IconData icone;
  final String titre;
  final String description;

  const CarteInfo({
    super.key,
    required this.icone,
    required this.titre,
    required this.description,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(12),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.1),
            blurRadius: 8,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Row(
        children: [
          CircleAvatar(
            radius: 24,
            backgroundColor: Colors.blue,
            child: Icon(icone, color: Colors.white, size: 28),
          ),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(titre,
                    style: const TextStyle(
                        fontSize: 18, fontWeight: FontWeight.bold)),
                const SizedBox(height: 4),
                Text(description,
                    style: TextStyle(color: Colors.grey[600])),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

6. TP de synthèse — Ma carte de visite interactive

import 'package:flutter/material.dart';

void main() => runApp(const CarteVisiteApp());

class CarteVisiteApp extends StatelessWidget {
  const CarteVisiteApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ma Carte de Visite',
      theme: ThemeData(primarySwatch: Colors.indigo, useMaterial3: true),
      home: const PageCarteVisite(),
    );
  }
}

class PageCarteVisite extends StatefulWidget {
  const PageCarteVisite({super.key});

  @override
  State<PageCarteVisite> createState() => _PageCarteVisiteState();
}

class _PageCarteVisiteState extends State<PageCarteVisite> {
  int _vues = 0; // Bonus : compteur de vues

  void _incrementerVues() {
    setState(() => _vues++);
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("👀 Vue n°$_vues enregistrée !"), duration: const Duration(seconds: 1)),
    );
  }

  void _envoyerMessage() {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text("Message envoyé !")),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Ma Carte de Visite")),
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 5. Bonus : appui long -> incrémente le compteur de vues
              GestureDetector(
                onLongPress: _incrementerVues,
                child: const CircleAvatar(
                  radius: 60,
                  backgroundColor: Colors.indigo,
                  child: Icon(Icons.person, size: 70, color: Colors.white),
                ),
              ),
              const SizedBox(height: 8),
              Text("Vues de la carte : $_vues", style: TextStyle(color: Colors.grey[600])),
              const SizedBox(height: 20),

              // 2. Nom + métier
              const Text(
                "Camille Dupont",
                style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
              ),
              Text(
                "Développeuse Mobile Flutter",
                style: TextStyle(fontSize: 16, color: Colors.grey[600]),
              ),
              const SizedBox(height: 24),

              // 3. Informations de contact
              const _LigneContact(icone: Icons.email, texte: "camille.dupont@email.com"),
              const _LigneContact(icone: Icons.phone, texte: "+33 6 12 34 56 78"),
              const _LigneContact(icone: Icons.location_on, texte: "Paris, France"),
              const SizedBox(height: 24),

              // 4. Bouton de contact
              ElevatedButton.icon(
                onPressed: _envoyerMessage,
                icon: const Icon(Icons.send),
                label: const Text("Me contacter"),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _LigneContact extends StatelessWidget {
  final IconData icone;
  final String texte;

  const _LigneContact({required this.icone, required this.texte});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 6),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icone, color: Colors.indigo, size: 20),
          const SizedBox(width: 10),
          Text(texte, style: const TextStyle(fontSize: 15)),
        ],
      ),
    );
  }
}

Explication et rendu sur un Smartphone :

représentation graphique de cette application de carte de visite interactive

Points clés du corrigé :


⬅️ Retour au Chapitre 1 ➡️ Chapitre 2