Se connecter à Flutter avec Node js MySQL
Sommaire
- 1- Objectif
- 2- Présentation
- 3- Configuration du projet
- 3.1- Étape 1 : Créer la base de données MySQL
- 3.2- Étape 2 : Créer le dossier du projet
- 3.2.1- Créer le dossier du projet
- 3.2.2- Initialiser le projet Node.js
- 3.2.3- Installer les modules nécessaires
- 3.3- Étape 3 : Créer les fichiers un par un
- 3.4- Étape 4 : Lancer les scripts
- 4- Créer le projet Flutter
- 4.1- Étape 4 : Créer l'interface de connexion Flutter
- 4.2- Étape 5 : Définir un modèle de données (Utilisateur)
- 5- CRUD (client) avec Node.js
- 5.1- Étape 1 : Créer le fichier
get_client.js
- 5.2- Étape 2 : Créer le fichier
insert_client.js
- 5.3- Étape 3 : Créer le fichier
update_client.js
- 5.4- Étape 4 : Créer le fichier
delete_client.js
- 5.5- Étape 5 : Créer le fichier
home_page.dart
- 6- Exercices
- 7- Fichiers réalisés
- 7.1.1- Cours Flutter
Se connecter à Flutter avec Node js MySQL
-
Objectif
- À la fin de ce tutoriel, vous serez capable de :
- Créer une base de données MySQL simple pour l’authentification.
- Écrire un backend Node.js pour la connexion à MySQL.
- Échanger des données entre Flutter et Node.js via HTTP.
- Créer une interface de connexion Flutter avec vérification côté serveur.
-
Présentation
- Ce tutoriel explique comment connecter une application Flutter à une base de données MySQL à l’aide d’un backend Node.js. Flutter ne peut pas accéder directement à MySQL, d’où l’importance de l’API Node.js.
-
Configuration du projet
-
Étape 1 : Créer la base de données MySQL
- Créer la base de données :
- Créer la table
utilisateurs
: -
Étape 2 : Créer le dossier du projet
-
Créer le dossier du projet
- Clique droit dans le dossier où tu veux travailler → Nouveau dossier
- Nomme-le par exemple : mon_api_node
- Clique droit sur ce dossier → Ouvrir avec Terminal (ou PowerShell ou Invite de commandes)
-
Initialiser le projet Node.js
- Dans le terminal ouvert dans le dossier mon_api_node, tape :
npm init -y
- Cela crée un fichier package.json.
-
Installer les modules nécessaires
- Toujours dans le terminal :
npm install express mysql2 bcrypt body-parser
-
Étape 3 : Créer les fichiers un par un
- Créer le fichier config.js :
- Enregistre le fichier sous config.js dans ton dossier mon_api_node
- Créer le fichier db.js :
- Ce code permet d’établir une connexion à une base de données MySQL en utilisant la bibliothèque mysql2 dans un projet Node.js.
- On importe la bibliothèque mysql2 qui fournit des fonctions pour interagir avec une base de données MySQL.
- On importe un fichier de configuration local config.js (ou .json) qui contient les paramètres de connexion à la base de données (hôte, utilisateur, mot de passe, etc.).
- On crée un objet connection qui représente la connexion à la base de données MySQL.
- Les paramètres utilisés sont :
- host: l’adresse du serveur MySQL (ex : localhost ou une IP)
- user: le nom d’utilisateur MySQL
- password: le mot de passe associé
- database: le nom de la base de données à utiliser
- charset: le jeu de caractères (ex : utf8mb4)
- Ici, on lance la connexion vers MySQL.
- Si la connexion échoue, on affiche l’erreur dans la console et on quitte le processus Node.js (process.exit(1)).
- Sinon, on affiche un message de succès.
- On exporte l’objet connection pour pouvoir le réutiliser dans d’autres fichiers du projet (par exemple, pour exécuter des requêtes SQL).
- Créer le fichier insert_user.js pour ajouter un utilisateur :
- Créer le fichier login.js :
- Ce code crée un serveur web avec Express qui écoute sur le port 3000 et propose un endpoint POST /login permettant à un utilisateur de se connecter via email et mot de passe. Les mots de passe sont sécurisés avec bcrypt.
- express pour créer le serveur HTTP.
- db est un module local qui gère la connexion à la base de données (MySQL).
- bcrypt pour comparer les mots de passe hashés.
- body-parser pour parser les requêtes JSON.
- On initialise une instance Express app.
- Middleware qui ajoute des headers CORS pour autoriser toutes les origines à accéder à l’API (utile pour le développement et appels cross-origin).
- Il permet aussi de dire que l’en-tête Content-Type est autorisé dans les requêtes.
- Le next() permet de passer au middleware suivant.
- Route POST /login : on récupère email et password depuis le corps JSON de la requête.
- Si l’un des deux est manquant, on répond immédiatement avec un message d’erreur.
- On interroge la base MySQL via db.query pour chercher un utilisateur par email.
- Si erreur ou aucun résultat, on renvoie un message d’authentification échouée.
- On récupère le premier utilisateur trouvé.
- On compare le mot de passe envoyé avec le hash stocké en base via bcrypt.compareSync.
- Si le mot de passe ne correspond pas, on renvoie une erreur.
- Si tout est valide, on renvoie un objet JSON avec success: true et les infos utilisateur (id, email, rôles convertis depuis JSON string).
- Démarre le serveur sur le port 3000 et affiche un message dans la console.
- Créer le fichier insert_userindex.js pour ajouter un utilisateur :
-
Étape 4 : Lancer les scripts
- Dans le terminal, tu fais :
- Pour insérer un utilisateur test :
node insert_user.js
- Pour lancer le serveur :
node login.js
-
Créer le projet Flutter
- Créer le projet Flutter :
- Ajouter la dépendance HTTP dans
pubspec.yaml
: -
Étape 4 : Créer l’interface de connexion Flutter
- Fichier : main.dart
- Description : Ce fichier est le point d’entrée de votre application Flutter. Il lance le widget
MainApp
qui affiche directement la page de connexionLoginPage
. - Fichier : login_page.dart
- Description : Ce fichier gère l’interface de connexion utilisateur. Lorsqu’un utilisateur saisit ses identifiants, l’application envoie une requête POST à l’API
Node.js
(route/login
). Si les identifiants sont valides, il est redirigé vers une autre page. -
Étape 5 : Définir un modèle de données (Utilisateur)
- Fichier :
client_model.dart
- Description : Cette classe représente un utilisateur. Elle permet de convertir les données reçues du backend Node.js (en JSON) en objet Dart, et inversement.
-
CRUD (client) avec Node.js
-
Étape 1 : Créer le fichier
get_client.js
- Ce fichier expose une route GET qui renvoie la liste des clients au format JSON depuis la base de données. L’application Flutter peut appeler cette route pour afficher les données clients.
-
Étape 2 : Créer le fichier
insert_client.js
- Ce fichier expose une route POST qui permet d’ajouter un nouveau client. Flutter envoie les données (name, family, age) en JSON, et le serveur les insère dans la base de données MySQL.
-
Étape 3 : Créer le fichier
update_client.js
- Ce fichier expose une route POST qui met à jour les informations d’un client existant dans la base de données. Flutter envoie l’identifiant du client et les nouvelles données (nom, famille, âge) en JSON via POST.
-
Étape 4 : Créer le fichier
delete_client.js
- Ce fichier expose une route POST qui permet de supprimer un client de la base de données. L’application Flutter envoie l’identifiant du client à supprimer en JSON.
-
Étape 5 : Créer le fichier
home_page.dart
- Ce fichier Flutter est la page principale de l’application. Il permet d’afficher la liste des clients, d’en ajouter, modifier ou supprimer via l’API Node.js.
-
Exercices
- Exercice 1 : Créer une page d’inscription Flutter + script PHP
register.php
. - Exercice 2 : Ajouter le hachage de mot de passe avec
password_hash()
côté PHP. - Exercice 3 : Créer une page de profil affichant l’email après connexion.
-
Fichiers réalisés
- connexion.php
- login.php
- main.dart
- pubspec.yaml
- Base de données MySQL : flutter_app / table users
CREATE DATABASE flutter_app;
CREATE TABLE `utilisateurs` (
`id` int NOT NULL AUTO_INCREMENT,
`email` varchar(180) NOT NULL,
`roles` json NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_EMAIL` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
module.exports = {
DB_HOST: '127.0.0.1',
DB_NAME: 'base_test',
DB_USER: 'root',
DB_PASSWORD: '',
DB_CHARSET: 'utf8mb4'
};
const mysql = require('mysql2');
const config = require('./config');
const connection = mysql.createConnection({
host: config.DB_HOST,
user: config.DB_USER,
password: config.DB_PASSWORD,
database: config.DB_NAME,
charset: config.DB_CHARSET
});
connection.connect(err => {
if (err) {
console.error("Erreur de connexion MySQL :", err.message);
process.exit(1);
} else {
console.log("Connexion MySQL réussie !");
}
});
module.exports = connection;
const mysql = require('mysql2');
const config = require('./config');
const connection = mysql.createConnection({
host: config.DB_HOST,
user: config.DB_USER,
password: config.DB_PASSWORD,
database: config.DB_NAME,
charset: config.DB_CHARSET
});
connection.connect(err => {
if (err) {
console.error("Erreur de connexion MySQL :", err.message);
process.exit(1);
} else {
console.log("Connexion MySQL réussie !");
}
});
module.exports = connection;
const db = require('./db');
const bcrypt = require('bcrypt');
const email = 'nodejs@example.com';
const password = bcrypt.hashSync('123456', 10);
const roles = JSON.stringify(['ROLE_USER']);
db.query(
"INSERT INTO utilisateurs (email, password, roles) VALUES (?, ?, ?)",
[email, password, roles],
(err, result) => {
if (err) {
console.error("Erreur d'insertion :", err.message);
} else {
console.log("Utilisateur ajouté avec ID :", result.insertId);
}
process.exit();
}
);
const express = require('express');
const db = require('./db');
const bcrypt = require('bcrypt');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
next();
});
app.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.json({ success: false, message: "Email ou mot de passe manquant." });
}
db.query("SELECT * FROM utilisateurs WHERE email = ?", [email], (err, results) => {
if (err || results.length === 0) {
return res.json({ success: false, message: "Identifiants invalides" });
}
const user = results[0];
const passwordMatch = bcrypt.compareSync(password, user.password);
if (!passwordMatch) {
return res.json({ success: false, message: "Mot de passe incorrect" });
}
return res.json({
success: true,
user: {
id: user.id,
email: user.email,
roles: JSON.parse(user.roles)
}
});
});
});
app.listen(3000, () => console.log("Serveur Node.js en écoute sur http://localhost:3000"));
const express = require('express');
const db = require('./db');
const bcrypt = require('bcrypt');
const bodyParser = require('body-parser');
const app = express();
On importe les modules nécessaires :
app.use(bodyParser.json());
On utilise body-parser pour que Express puisse comprendre les données JSON envoyées dans le corps des requêtes (important pour POST).
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
next();
});
app.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.json({ success: false, message: "Email ou mot de passe manquant." });
}
db.query("SELECT * FROM utilisateurs WHERE email = ?", [email], (err, results) => {
if (err || results.length === 0) {
return res.json({ success: false, message: "Identifiants invalides" });
}
const user = results[0];
const passwordMatch = bcrypt.compareSync(password, user.password);
if (!passwordMatch) {
return res.json({ success: false, message: "Mot de passe incorrect" });
}
return res.json({
success: true,
user: {
id: user.id,
email: user.email,
roles: JSON.parse(user.roles)
}
});
});
});
app.listen(3000, () => console.log("Serveur Node.js en écoute sur http://localhost:3000"));
const express = require('express');
const cors = require('cors');
const app = express();
// Configuration CORS critique
app.use(cors({
origin: '*',
methods: ['POST', 'GET', 'OPTIONS'],
allowedHeaders: ['Content-Type'],
credentials: true
}));
// Middleware pour vérifier les requêtes entrantes
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
// Route de login test
app.post('/login', (req, res) => {
console.log('Headers reçus:', req.headers);
console.log('Corps reçu:', req.body);
res.status(200).json({ status: 'success', ip: req.ip });
});
// Démarrer avec gestion d'erreur
app.listen(3000, '0.0.0.0', () => {
console.log('EN ÉCOUTE SUR 0.0.0.0:3000 - PRÊT');
}).on('error', (err) => {
console.error('ERREUR SERVEUR:', err);
});
flutter create flutter_login_app
dependencies:
flutter:
sdk: flutter
http: ^0.13.5
import 'package:flutter/material.dart';
import 'login_page.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginPage(),
);
}
}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'home_page.dart'; // Import de ta page Home
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _emailCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
String errorMsg = '';
// Utilise l'IP de ta machine, ici la même que dans ta page Home
final String serverUrl = "http://10.0.2.2:3000/login";
Future<void> _login() async {
try {
final response = await http.post(
Uri.parse(serverUrl),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'email': _emailCtrl.text,
'password': _passwordCtrl.text,
}),
);
print('Statut: ${response.statusCode}');
print('Corps: ${response.body}');
final data = jsonDecode(response.body);
if (data['status'] == 'success') {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else {
setState(() {
errorMsg = data['message'] ?? 'Identifiants incorrects';
});
}
} catch (e) {
print('Erreur: $e');
setState(() {
errorMsg = 'Erreur de connexion au serveur.';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Login")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _emailCtrl,
decoration: const InputDecoration(labelText: "Email"),
),
TextField(
controller: _passwordCtrl,
decoration: const InputDecoration(labelText: "Password"),
obscureText: true,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _login,
child: const Text("Login"),
),
if (errorMsg.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Text(errorMsg, style: const TextStyle(color: Colors.red)),
),
],
),
),
);
}
}
class Client {
final int id;
final String name;
final String family;
final int age;
Client({
required this.id,
required this.name,
required this.family,
required this.age,
});
factory Client.fromJson(Map json) {
return Client(
id: json['id'] as int? ?? 0, // Gestion null-safe
name: (json['name'] ?? '') as String,
family: (json['family'] ?? '') as String,
age: (json['age'] ?? 0) as int,
);
}
Map toJson() => {
'id': id,
'name': name,
'family': family,
'age': age,
};
}
const express = require('express');
const db = require('./db');
const app = express();
// Autorisation CORS
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Content-Type", "application/json");
next();
});
// Route pour obtenir tous les clients
app.get('/clients', (req, res) => {
db.query("SELECT * FROM client ORDER BY id DESC", (err, results) => {
if (err) {
return res.status(500).json({ success: false, message: "Erreur serveur" });
}
res.json(results);
});
});
app.listen(3000, () => console.log("API clients démarrée sur http://localhost:3000"));
const express = require('express');
const db = require('./db');
const app = express();
// Middleware pour lire le JSON envoyé par Flutter
app.use(express.json());
// CORS (Cross-Origin)
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
next();
});
// Gestion des pré-requêtes CORS
app.options('/clients', (req, res) => {
res.sendStatus(200);
});
// Route POST pour insérer un client
app.post('/clients', (req, res) => {
const { name, family, age } = req.body;
// Vérification des données
if (!name || !family) {
return res.json({ success: false, message: "Champs manquants" });
}
// Insertion dans la base
const sql = "INSERT INTO client (name, family, age) VALUES (?, ?, ?)";
db.query(sql, [name, family, age], (err, result) => {
if (err) {
return res.status(500).json({ success: false, message: "Erreur insertion" });
}
res.json({ success: true, message: "Ajouté avec succès" });
});
});
app.listen(3000, () => console.log("API insertion client disponible sur http://localhost:3000"));
const express = require('express');
const db = require('./db');
const app = express();
// Middleware JSON
app.use(express.json());
// CORS
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
next();
});
// Route OPTIONS pour pré-vol CORS
app.options('/clients/update', (req, res) => {
res.sendStatus(200);
});
// Route POST pour mettre à jour un client
app.post('/clients/update', (req, res) => {
const { id, name, family, age } = req.body;
// Validation
if (!id || id <= 0) {
return res.json({ success: false, message: "ID invalide" });
}
const sql = "UPDATE client SET name = ?, family = ?, age = ? WHERE id = ?";
db.query(sql, [name, family, age, id], (err, result) => {
if (err) {
return res.status(500).json({ success: false, message: "Erreur de mise à jour" });
}
const updated = result.affectedRows > 0;
res.json({
success: updated,
message: updated ? "Mis à jour avec succès" : "Aucune modification"
});
});
});
app.listen(3000, () => console.log("API mise à jour client en écoute sur http://localhost:3000"));
const express = require('express');
const db = require('./db');
const app = express();
// Middleware JSON
app.use(express.json());
// CORS
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
next();
});
// Route OPTIONS pour le pré-vol CORS
app.options('/clients/delete', (req, res) => {
res.sendStatus(200);
});
// Route POST pour supprimer un client
app.post('/clients/delete', (req, res) => {
const { id } = req.body;
// Vérification de l'ID
if (!id || id <= 0) {
return res.json({ success: false, message: "ID invalide" });
}
const sql = "DELETE FROM client WHERE id = ?";
db.query(sql, [id], (err, result) => {
if (err) {
return res.status(500).json({ success: false, message: "Erreur de suppression" });
}
const deleted = result.affectedRows > 0;
res.json({
success: deleted,
message: deleted ? "Supprimé avec succès" : "Aucun client supprimé"
});
});
});
app.listen(3000, () => console.log("API suppression client en écoute sur http://localhost:3000"));
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'client_model.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State createState() => _HomePageState();
}
class _HomePageState extends State {
final _nameCtrl = TextEditingController();
final _familyCtrl = TextEditingController();
final _ageCtrl = TextEditingController();
String serverMsg = '';
final List _clientsList = [];
final String baseUrl = "http://10.0.2.2:3000"; // Remplacez par votre IP et port Node.js si nécessaire
Future _postRequest() async {
try {
final response = await http.post(
Uri.parse("$baseUrl/add-client"),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'name': _nameCtrl.text.trim(),
'family': _familyCtrl.text.trim(),
'age': int.tryParse(_ageCtrl.text) ?? 0,
}),
);
final responseData = jsonDecode(response.body);
_clearForm();
if (response.statusCode == 201 && responseData['success']) {
serverMsg = "Ajouté avec ID: ${responseData['id']}";
} else {
serverMsg = "Erreur: ${responseData['message']}";
}
} on http.ClientException catch (e) {
serverMsg = "Erreur réseau: ${e.message}";
} on FormatException {
serverMsg = "Réponse serveur invalide";
} catch (e) {
serverMsg = "Erreur inattendue: $e";
}
setState(() {});
_getRequest();
}
Future _getRequest() async {
try {
final response = await http.get(Uri.parse("$baseUrl/clients"));
if (response.statusCode == 200 || response.statusCode == 201) {
final List jsonData = jsonDecode(response.body);
_clientsList.clear();
_clientsList.addAll(jsonData.map((e) => Client.fromJson(e)));
serverMsg = "Données chargées";
} else {
serverMsg = "Échec du chargement";
}
} catch (e) {
serverMsg = "Erreur: $e";
}
setState(() {});
}
Future _deleteRequest(int id) async {
try {
final response = await http.delete(
Uri.parse("$baseUrl/clients/$id"),
);
if (response.statusCode == 200) {
serverMsg = "Supprimé avec succès";
_getRequest();
} else {
serverMsg = "Échec de la suppression";
}
} catch (e) {
serverMsg = "Erreur: $e";
}
setState(() {});
}
Future _updateRequest(Client client) async {
try {
final response = await http.put(
Uri.parse("$baseUrl/clients/${client.id}"),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(client.toJson()),
);
if (response.statusCode == 200) {
serverMsg = "Mis à jour avec succès";
_getRequest();
} else {
serverMsg = "Échec de la mise à jour";
}
} catch (e) {
serverMsg = "Erreur: $e";
}
setState(() {});
}
void _clearForm() {
_nameCtrl.clear();
_familyCtrl.clear();
_ageCtrl.clear();
}
@override
void initState() {
super.initState();
_getRequest();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("CRUD Node.js")),
body: SafeArea(
child: Column(
children: [
Container(
height: 300,
color: Colors.grey[200],
padding: const EdgeInsets.all(20),
child: Column(
children: [
TextField(
controller: _nameCtrl,
decoration: const InputDecoration(labelText: 'Name'),
),
TextField(
controller: _familyCtrl,
decoration: const InputDecoration(labelText: 'Family'),
),
TextField(
controller: _ageCtrl,
decoration: const InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
Text(
serverMsg,
style: const TextStyle(color: Colors.blue),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _clientsList.length,
itemBuilder: (context, index) {
final client = _clientsList[index];
return ListTile(
title: Text("${client.name} ${client.family}"),
subtitle: Text("Age: ${client.age}"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit, color: Colors.green),
onPressed: () {
_nameCtrl.text = client.name;
_familyCtrl.text = client.family;
_ageCtrl.text = client.age.toString();
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: const Text("Modifier le client"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _nameCtrl,
decoration: const InputDecoration(labelText: "Name"),
),
TextField(
controller: _familyCtrl,
decoration: const InputDecoration(labelText: "Family"),
),
TextField(
controller: _ageCtrl,
decoration: const InputDecoration(labelText: "Age"),
),
],
),
actions: [
TextButton(
child: const Text("Annuler"),
onPressed: () => Navigator.pop(context),
),
ElevatedButton(
child: const Text("Mettre à jour"),
onPressed: () {
_updateRequest(Client(
id: client.id,
name: _nameCtrl.text,
family: _familyCtrl.text,
age: int.tryParse(_ageCtrl.text) ?? 0,
));
Navigator.pop(context);
},
),
],
);
},
);
},
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () {
_deleteRequest(client.id);
},
),
],
),
);
},
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _postRequest,
child: const Icon(Icons.add),
),
);
}
}