TP01 Création d’une Application de Liste de Produits en Flutter
TP01 Création d’une Application de Liste de Produits en Flutter
-
Présentation
- Dans ce travail pratique (TP), vous allez découvrir le développement d’applications mobiles en utilisant le framework Flutter.
- Flutter est un outil puissant qui vous permet de créer des applications multiplateformes avec une seule base de code.
- Dans ce TP, vous allez créer une application qui affiche une liste de produits avec leurs détails. Vous allez apprendre à structurer une application, à afficher des éléments d’interface utilisateur et à organiser le contenu de manière efficace.
-
Objectif
- L’objectif de ce TP est de vous familiariser avec les concepts de base du développement d’applications en Flutter. À la fin de ce TP, vous devriez être en mesure de :
- Configurer un projet Flutter : Créer un projet Flutter et configurer l’environnement de développement.
- Comprendre la Structure de Base : Comprendre comment les widgets et les classes sont organisés dans une application Flutter.
- Créer une Liste de Produits : Utiliser des widgets pour afficher une liste de produits avec leurs détails.
- Personnaliser l’Interface : Personnaliser l’apparence de l’application en modifiant le thème et en utilisant différents widgets d’interface utilisateur.
- Gérer les Ressources : Apprendre à gérer les images et les autres ressources nécessaires pour votre application.
-
Instructions
- Suivez les étapes fournies dans le document du TP pour créer l’application. Chaque étape vous guidera à travers le processus, en vous demandant de créer des dossiers, d’ajouter du code et de comprendre la logique sous-jacente.
- N’hésitez pas à poser des questions si vous rencontrez des difficultés ou si vous souhaitez explorer des aspects plus avancés.
- Le résultat final de l’application est le suivant :
-
Remarque
- Ce TP vous donne une opportunité d’expérimenter par vous-même et de consolider vos compétences en développement Flutter. N’hésitez pas à explorer davantage une fois que vous aurez terminé les étapes principales. L’objectif est de vous familiariser avec le processus de développement et les concepts clés, ce qui vous préparera à aborder des projets plus complexes à l’avenir.
-
Etapes de réalisation (Partie 01)
-
Étape 1: Présentation du Projet
- Vous allez créer une application Flutter qui affiche une liste de produits avec leurs détails.
-
Étape 2: Mise en Place du Projet
- Créez un nouveau projet Flutter dans Visual Studio Code.
-
Étape 3: Configuration de la Structure de Base.
- Remplacez le contenu du fichier lib/main.dart par le modèle de base fourni.
-
Étape 4: Ajout des Images
- Créez un dossier « assets » à la racine du projet.
- À l’intérieur du dossier « assets », créez un dossier « appimages ».
- Placez les images (iphone.png, disque_dur.png, laptop.png, tablet.png, souris.png, floppy.png) dans le dossier « appimages ».
-
Étape 5: Ajout des Dépendances
- Dans le fichier pubspec.yaml, ajoutez la dépendance nécessaire pour utiliser le package flutter/material.
-
Étape 6: Compréhension de la Structure de la Liste
- Identifiez les classes MyApp, MyHomePage et ProductBox dans le code.
- Comprenez quelle classe représente l’application, la page d’accueil et les éléments de la liste de produits.
-
Étape 7: Construction de la Liste de Produits
- À l’intérieur de la méthode build de MyHomePage, utilisez un ListView.builder pour afficher la liste de produits en utilisant le widget ListTile.
-
Étape 8: Création de la Classe ProductBox
- Créez une nouvelle classe ProductTile pour afficher les détails de chaque produit. Cette classe devrait prendre en compte les informations nécessaires pour un produit (nom, description, prix, image) et utiliser le widget ListTile pour afficher ces informations de manière structurée.
-
Étape 9: Affichage des Informations du Produit
- À l’intérieur de la classe ProductTile, affichez les informations du produit (nom, description, prix, image) en utilisant des widgets appropriés. Utilisez le widget ListTile pour structurer l’affichage de ces informations.
-
Étape 10: Exécution de l’Application
- Exécutez l’application pour voir la liste de produits affichée correctement.
-
Étape 11: Bonus – Personnalisation du Thème
- Explorez le fichier lib/main.dart pour trouver où le thème de l’application est défini.
- Modifiez les couleurs ou d’autres propriétés du thème selon vos préférences.
- À la fin de ces étapes, vous devriez avoir une application qui affiche une liste de produits avec leurs détails. N’hésitez pas à explorer et à expérimenter davantage pour approfondir votre compréhension de Flutter.
-
Étapes de réalisation (Partie 02)
-
Filtrage et Recherche
- Intégrez des fonctionnalités de filtrage et de recherche pour permettre aux utilisateurs de trouver des produits plus facilement. Par exemple, ajoutez des boutons de filtre pour trier les produits par catégorie ou par prix.
- Étape 1: Ajout de Contrôleurs et d’État pour la Recherche
- Dans la classe MyHomePage, ajoutez un contrôleur de texte pour la recherche :
- Dans le build de MyHomePage, ajoutez un champ de texte pour la recherche au-dessus de la liste de produits :
- Étape 2: Filtrage des Produits
- Dans la classe MyHomePage, créez une liste filtrée pour stocker les produits en fonction des critères de recherche :
- Mettez à jour le itemBuilder de votre ListView.builder pour utiliser la liste filtrée filteredProducts au lieu de productList :
- Étape 3: Implémentation du Filtrage et de la Recherche
- Écoutez les changements dans le contrôleur de texte pour mettre à jour la liste filtrée en fonction de la recherche :
- Étape 4: Ajout de Boutons de Filtrage
- Ajoutez des boutons de filtre dans la barre d’applications pour trier les produits par catégorie ou par prix :
- En ajoutant ces étapes, vous permettez aux utilisateurs de rechercher des produits en fonction de mots-clés et de filtrer les produits par nom ou par prix.
- N’hésitez pas à personnaliser davantage ces fonctionnalités en fonction des besoins de votre application.
-
Étapes de réalisation (Partie 03)
- Question : Comment pouvez-vous ajouter une fonctionnalité pour inverser l’ordre de tri lorsque l’utilisateur clique à nouveau sur les boutons de tri, permettant ainsi de passer d’un tri croissant à un tri décroissant et vice versa ?
-
Filtrage et Recherche
- Étape 1: Ajout de l’État de Tri Réversible
- Dans la classe MyHomePageState, créez des variables d’état pour chaque type de tri (prix et nom) en utilisant bool pour indiquer l’ordre de tri actuel, par exemple :
- Ces variables d’état seront utilisées pour suivre l’ordre de tri actuel pour chaque type de tri.
- Étape 2: Modification des Fonctions de Tri
- Modifiez les fonctions de tri existantes pour tenir compte de l’ordre de tri réversible en fonction de l’état actuel :
- Étape 3: Mise à Jour des Boutons de Tri
- Modifiez les boutons de tri pour appeler les nouvelles fonctions de tri et mettre à jour l’icône en fonction de l’ordre de tri actuel :
- Ainsi, lorsque les utilisateurs cliquent à nouveau sur les boutons de tri, l’ordre de tri sera inversé, ce qui permet de passer d’un tri croissant à un tri décroissant et vice versa.
Solution
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: const [
ProductTile(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"),
ProductTile(
name: "disque_dur",
description: "disque_dur is the most featureful phone ever",
price: 800,
image: "disque_dur.png"),
ProductTile(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"),
ProductTile(
name: "Tablet",
description: "Tablet is the most useful device ever formeeting",
price: 1500,
image: "tablet.png"),
ProductTile(
name: "souris",
description: "souris is useful storage medium",
price: 100,
image: "souris.png"),
ProductTile(
name: "flash_disque",
description: "flash_disque is useful rescue storage medium",
price: 20,
image: "flash_disque.png"),
],
),
);
}
}
class ProductTile extends StatelessWidget {
const ProductTile({
Key? key,
required this.name,
required this.description,
required this.price,
required this.image,
}) : super(key: key);
final String name;
final String description;
final int price;
final String image;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Image.asset("assets/appimages/$image"),
title: Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(description),
Text("Price: $price"),
],
),
);
}
}
final TextEditingController _searchController = TextEditingController();
TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search...',
),
),
List filteredProducts = productList;
itemBuilder: (context, index) {
return ProductTile(
name: filteredProducts[index].name,
description: filteredProducts[index].description,
price: filteredProducts[index].price,
image: filteredProducts[index].image,
);
},
_searchController.addListener(() {
String searchTerm = _searchController.text.toLowerCase();
if (searchTerm.isEmpty) {
filteredProducts = productList;
} else {
filteredProducts = productList.where((product) =>
product.name.toLowerCase().contains(searchTerm) ||
product.description.toLowerCase().contains(searchTerm)).toList();
}
// Mettez à jour l'interface en utilisant setState
setState(() {});
});
actions: [
IconButton(
icon: Icon(Icons.sort_by_alpha),
onPressed: () {
filteredProducts.sort((a, b) => a.name.compareTo(b.name));
setState(() {});
},
),
IconButton(
icon: Icon(Icons.attach_money),
onPressed: () {
filteredProducts.sort((a, b) => a.price.compareTo(b.price));
setState(() {});
},
),
],
Solution
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final TextEditingController _searchController = TextEditingController();
List filteredProducts = productList;
@override
void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}
void _onSearchChanged() {
String searchTerm = _searchController.text.toLowerCase();
if (searchTerm.isEmpty) {
setState(() {
filteredProducts = productList;
});
} else {
setState(() {
filteredProducts = productList.where((product) =>
product.name.toLowerCase().contains(searchTerm) ||
product.description.toLowerCase().contains(searchTerm)).toList();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Product Listing"),
actions: [
IconButton(
icon: Icon(Icons.sort_by_alpha),
onPressed: () {
setState(() {
filteredProducts.sort((a, b) => a.name.compareTo(b.name));
});
},
),
IconButton(
icon: Icon(Icons.attach_money),
onPressed: () {
setState(() {
filteredProducts.sort((a, b) => a.price.compareTo(b.price));
});
},
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search...',
),
),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
itemCount: filteredProducts.length,
itemBuilder: (context, index) {
return ProductTile(
name: filteredProducts[index].name,
description: filteredProducts[index].description,
price: filteredProducts[index].price,
image: filteredProducts[index].image,
);
},
),
),
],
),
);
}
}
class ProductTile extends StatelessWidget {
const ProductTile({
Key? key,
required this.name,
required this.description,
required this.price,
required this.image,
}) : super(key: key);
final String name;
final String description;
final int price;
final String image;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Image.asset("assets/appimages/$image"),
title: Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(description),
Text("Price: $price"),
],
),
);
}
}
class Product {
final String name;
final String description;
final int price;
final String image;
Product({
required this.name,
required this.description,
required this.price,
required this.image,
});
}
List productList = [
Product(
name: "iPhone",
description: "iPhone is the stylish phone ever",
price: 1000,
image: "iphone.png",
),
Product(
name: "disque_dur",
description: "disque_dur is the most featureful phone ever",
price: 800,
image: "disque_dur.png",
),
Product(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png",
),
Product(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.png",
),
Product(
name: "souris",
description: "souris is useful storage medium",
price: 100,
image: "souris.png",
),
Product(
name: "flash_disque",
description: "flash_disque is useful rescue storage medium",
price: 20,
image: "flash_disque.png",
),
];
bool sortByPriceAscending = true;
bool sortByNameAscending = true;
void _sortProductsByPrice() {
setState(() {
if (sortByPriceAscending) {
// Tri croissant par prix
} else {
// Tri décroissant par prix
}
sortByPriceAscending = !sortByPriceAscending; // Inversion de l'ordre
});
}
void _sortProductsByName() {
setState(() {
if (sortByNameAscending) {
// Tri croissant par nom
} else {
// Tri décroissant par nom
}
sortByNameAscending = !sortByNameAscending; // Inversion de l'ordre
});
}
IconButton(
icon: Icon(
sortByPriceAscending
? Icons.attach_money
: Icons.money_off,
),
onPressed: _sortProductsByPrice,
),
IconButton(
icon: Icon(
sortByNameAscending
? Icons.sort_by_alpha
: Icons.sort_by_alpha_outlined,
),
onPressed: _sortProductsByName,
),
Solution
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final TextEditingController _searchController = TextEditingController();
List filteredProducts = productList;
bool sortByPriceAscending = true;
bool sortByNameAscending = true;
@override
void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}
void _onSearchChanged() {
String searchTerm = _searchController.text.toLowerCase();
if (searchTerm.isEmpty) {
setState(() {
filteredProducts = productList;
});
} else {
setState(() {
filteredProducts = productList.where((product) =>
product.name.toLowerCase().contains(searchTerm) ||
product.description.toLowerCase().contains(searchTerm)).toList();
});
}
}
void _sortProductsByPrice() {
setState(() {
if (sortByPriceAscending) {
filteredProducts.sort((a, b) => a.price.compareTo(b.price));
} else {
filteredProducts.sort((a, b) => b.price.compareTo(a.price));
}
sortByPriceAscending = !sortByPriceAscending;
});
}
void _sortProductsByName() {
setState(() {
if (sortByNameAscending) {
filteredProducts.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
//filteredProducts.sort((a, b) => a.name.compareTo(b.name));
} else {
filteredProducts.sort((a, b) => b.name.toLowerCase().compareTo(a.name.toLowerCase()));
//filteredProducts.sort((a, b) => b.name.compareTo(a.name));
}
sortByNameAscending = !sortByNameAscending;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Product Listing"),
actions: [
IconButton(
icon: Icon(
sortByPriceAscending
? Icons.attach_money
: Icons.money_off,
),
onPressed: _sortProductsByPrice,
),
IconButton(
icon: Icon(
sortByNameAscending
? Icons.sort_by_alpha
: Icons.sort_by_alpha_outlined,
),
onPressed: _sortProductsByName,
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: 'Search...',
),
),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
itemCount: filteredProducts.length,
itemBuilder: (context, index) {
return ProductTile(
name: filteredProducts[index].name,
description: filteredProducts[index].description,
price: filteredProducts[index].price,
image: filteredProducts[index].image,
);
},
),
),
],
),
);
}
}
class ProductTile extends StatelessWidget {
const ProductTile({
Key? key,
required this.name,
required this.description,
required this.price,
required this.image,
}) : super(key: key);
final String name;
final String description;
final int price;
final String image;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Image.asset("assets/appimages/$image"),
title: Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(description),
Text("Price: $price"),
],
),
);
}
}
class Product {
final String name;
final String description;
final int price;
final String image;
Product({
required this.name,
required this.description,
required this.price,
required this.image,
});
}
List productList = [
Product(
name: "iPhone",
description: "iPhone is the stylish phone ever",
price: 1000,
image: "iphone.png",
),
Product(
name: "disque_dur",
description: "disque_dur is the most featureful phone ever",
price: 800,
image: "disque_dur.png",
),
Product(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png",
),
Product(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.png",
),
Product(
name: "souris",
description: "souris is useful storage medium",
price: 100,
image: "souris.png",
),
Product(
name: "flash_disque",
description: "flash_disque is useful rescue storage medium",
price: 20,
image: "flash_disque.png",
),
];