Vérifiez chaque point avant de continuer :
flutter doctor
flutter create hello_flutter
flutter devices
flutter run
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).
// 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)
Rendu possible du code ci-dessous :
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.
FloatingActionButton
heroTag
Hero
Plus simple à réaliser, voici pour vous aider, il vous reste à coder ;)
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])), ], ), ), ], ), ); } }
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 :
Points clés du corrigé :
int
_vues
setState()
StatefulWidget
GestureDetector
onTap
onLongPress
onDoubleTap
ScaffoldMessenger.of(context).showSnackBar(...)
SingleChildScrollView