En tant que développeur, savoir développer des applications mobiles c’est bien, mais un développeur mobile doit avant tout savoir consommer des API REST. Cette tâche est presque incontournable. Aujourd’hui, nous allons voir ensemble comment intégrer des APIs dans nos applications Flutter.
Nous nous baserons sur une API que nous avons déjà développée précédemment dans l’article « Créez une API REST avec LARAVEL » et sur le projet mobile « Utiliser une base de données dans une application Flutter », dans lequel nous avons vu comment travailler avec une base de données dans une application Flutter. Nous allons réorganiser ce projet afin de le connecter à notre API Laravel.
Organisation des projets
API REST
Connexion à la base de données
Pour commencer, ouvrez le code de notre projet Laravel dans un éditeur de text. Une fois ouvert, nous devons nous assurer que notre application est connectée à une base de données. Pour se faire, nous allons ouvrir et éditer, le fichier « .env ». Dans ce fichier, les paramètres à modifier sont les suivants :
DB_DATABASE=bibliotheca_api
DB_USERNAME=nom_utilisateur_base_de_donees
DB_PASSWORD=mot_de_passe
Initialisation de la base de données
Pour initialiser la base de données, nous devons d’abord la créer dans notre SGBD (Système de Gestion de Base de Données). Nous utiliserons dans cet article le SGBD MySQL. Cependant, vous pouvez utiliser le SGBD de votre choix à condition d’adapter la configuration du projet au SGBD choisi.
Nous pouvons maintenant l’initialiser, c’est-à-dire créer automatiquement les tables en exécutant la commande suivante dans le terminal de votre éditeur (Veillez à vous positionner à la racine de votre projet) :
php artisan migrate
Ainsi, voici le résultat attendu dans phpMyAdmin :
Lancement de l’application
Une fois terminé, toujours dans le terminal présent dans votre éditeur, démarrez l’application en tapant la commande suivante :
php artisan serve --host=0.0.0.0
Cette commande permet de démarrer notre application sur le port par défaut (8000) en spécifiant que notre application doit accepter les requêtes venant depuis toutes les adresses IP assignées à la machine sur laquelle l’application est exécutée (--host=0.0.0.0
).
Application mobile
Téléchargement des dépendances
Dans cette partie, nous devons également ouvrir le projet dans un autre éditeur (Sans pour autant fermer l’autre projet). Nous allons ensuite exécuter la commande suivante dans le terminal pour télécharger ou mettre à jour les dépendances (Veillez à vous positionner à la racine de votre projet) :
flutter pub get
Organisation du code
Maintenant que notre projet est prêt, nous allons faire un peu de ménage. Dans l’article où nous avions configuré ce projet, nous avons installé des dépendances pour créer et utiliser une base de données locale. Comme nous voulons maintenant travailler avec une API REST, nous allons supprimer tous les codes se référant à la configuration de la base de données.
Dans le fichier pubspec.yaml
, vous devez supprimer les lignes suivantes :
sqflite: ^2.0.3+1
path: ^1.8.1
Enregistrez et fermez le fichier. Vous devrez remarquer qu’il y a des erreurs dans le dossier “database”.
- Il faudra ensuite supprimer le dossier “database”.
- Dans le fichier
main.dart
:
Supprimez tout le code et ajoutez le code suivant :
import 'package:bibliotheca/views/home_page.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
title: "Bibliotheca",
theme: ThemeData(primaryColor: Colors.blue),
home: const HomePage(),
),
);
}
Initialisation du projet
- Installation des packages nécessaires dans le fichier
pubspec.yaml
:
Ajoutez le code suivant http: ^1.2.1
comme ceci :
dependencies:
flutter:
sdk: flutter
http: ^1.2.1
Vous devez ensuite enregistrer le fichier et exécuter dans le terminal de votre éditeur la commande :
flutter pub get
pour télécharger la nouvelle dépendance installée dans le projet.
Création des points d’entrée et de sortie des APIs
Dans le dossier “lib”, nous allons créer un dossier nommé “api”. Dans ce dossier, nous allons créer plusieurs fichiers :
auteur_api_ctl.dart
: Dans ce fichier, nous aurons les méthodes qui appelleront les APIs des auteurs.livre_api_ctl.dart
: Dans ce fichier, nous aurons les méthodes qui appelleront les APIs des livres.categorie_api_ctl.dart
: Dans ce fichier, nous aurons les méthodes qui appelleront les APIs des catégories.
Voici un aperçu de ces classes :
abstract class AuteurApiCtl {}
abstract class LivreApiCtl {}
abstract class CategorieApiCtl {}
Code des classes d’appel des APIs
Nous allons maintenant ajouter les différentes méthodes qui nous permettront d’appeler les APIs exposées par notre application Laravel. Nous verrons ensemble l’exemple des APIs d’auteur. Ensuite, vous pourrez reproduire le même exemple pour les autres APIs.
Méthodes de la classe AuteurApiCtl
Dans la classe AuteurApiCtl
, nous aurons les méthodes suivantes :
getAll()
: pour obtenir la liste des auteurscreate()
: pour créer un auteurupdate()
: pour mettre à jour un auteurdelete()
: pour supprimer un auteur
Notez que les autres classes auront elles aussi au moins ces quatre méthodes.
Classe outil de communication entre couches
Une fois que les méthodes des classes d’appel des APIs ont reçu les données, elles devront les transformer en classe modèle pour les transférer à notre vue. Cependant, elles ne font pas seulement que transmettre des classes modèles. Elles doivent également transmettre des erreurs. Nous allons donc créer une classe qui nous permettra de circuler toutes ces données entre les couches API et vue. Nous allons créer un dossier “tools” dans le dossier “lib” et dans ce dossier “tools”, nous allons créer un fichier api_data.dart
, puis une classe ApiData
dans le fichier. Voici un aperçu du code de cette classe.
class ApiData<T> {
// Ce champ permettra de savoir si l'appel de l'API a réussi ou non
bool status = false;
// Ce champ permettra de saisir un message d'erreur en cas d'échec de l'appel
// Nous lui assignons une valeur par défaut
String? message = "Désolé, une erreur est survenue";
// Ce champ permettra de sauvegarder les données reçues de l'API
T? data;
// Ce constructeur sera appelé dans le cas où l'appel de l'API a réussi
ApiData.success({required this.data})
: status = true,
message = null;
// Ce constructeur sera appelé dans le cas où l'appel de l'API a échoué
ApiData.error({this.message})
: status = false,
data = null;
}
Classe des constantes pour l’API ApiConst
Nous allons aussi créer une classe ApiConst
qui aura un champ header contenant les en-têtes HTTP et une méthode pour former l’URL d’une API. Voici le code de la classe :
abstract class ApiConst {
// Création d'une nouvelle URL à partir d'un module et d'un path
static Uri baseUrl({required String module, String path = ""}) {
return Uri.parse("http://localhost:3000/api/$module/$path");
}
// Création d'un header
static Map<String, String> header = {
"Accept": "application/json",
"Content-Type": "application/json",
};
}
Voici un aperçu de la classe AuteurApiCtl
:
import 'dart:convert';
import 'package:bibliotheca/models/auteur.dart';
import 'package:bibliotheca/tools/api_const.dart';
import 'package:bibliotheca/tools/api_data.dart';
import 'package:http/http.dart' as http;
abstract class AuteurApiCtl {
static const _module = "auteur";
static Future<ApiData<List<Auteur>>> getAll() async {
try {
var res = await http.get(
ApiConst.baseUrl(module: _module),
headers: ApiConst.header,
);
if (res.statusCode == 200) {
var body = jsonDecode(res.body);
return ApiData.success(data: Auteur.fromListJson(body));
} else {
return ApiData.error(message: res.reasonPhrase);
}
} catch (e) {
return ApiData.error();
}
}
static Future<ApiData<Auteur>> create(Auteur auteur) async {
try {
var res = await http.post(
ApiConst.baseUrl(module: _module, path: "create"),
headers: ApiConst.header,
body: jsonEncode(auteur.toJson()),
);
if (res.statusCode == 200) {
var body = jsonDecode(res.body);
return ApiData.success(data: Auteur.fromJson(body));
} else {
return ApiData.error();
}
} catch (e) {
return ApiData.error();
}
}
static Future<ApiData<Auteur>> update(Auteur auteur) async {
try {
var res = await http.post(
ApiConst.baseUrl(module: _module, path: "update"),
headers: ApiConst.header,
body: jsonEncode(auteur.toJson()),
);
if (res.statusCode == 200) {
var body = jsonDecode(res.body);
return ApiData.success(data: Auteur.fromJson(body));
} else {
return ApiData.error();
}
} catch (e) {
return ApiData.error();
}
}
static Future<ApiData<bool>> delete(int id) async {
try {
var res = await http.post(
ApiConst.baseUrl(module: _module, path: "delete/$id"),
headers: ApiConst.header,
);
if (res.statusCode == 200) {
return ApiData.success(data: true);
} else {
return ApiData.error();
}
} catch (e) {
return ApiData.error();
}
}
}
Le code des modèles a également subi une modification. Voici donc l’aperçu du code du modèle Auteur
:
class Auteur {
int? id;
String? nom;
String? prenoms;
String? email;
Auteur({this.id, this.nom, this.prenoms, this.email});
Auteur.fromJson(Map<String, dynamic> json) {
id = json["id"];
nom = json["nom"];
prenoms = json["prenoms"];
email = json["email"];
}
static List<Auteur> fromListJson(List data) {
return data.map((e) => Auteur.fromJson(e)).toList();
}
Map<String, dynamic> toJson() {
Map<String, dynamic> map = {};
map["id"] = id;
map["nom"] = nom;
map["prenoms"] = prenoms;
map["email"] = email;
return map;
}
}
Ainsi, voici les méthodes des classes qui nous permettront d’appeler nos APIs. Nous allons maintenant utiliser ces classes d’appel d’API pour les consommer depuis nos vues.
Code des vues de notre application
Nous verrons ensemble le code des vues de liste et d’édition des auteurs. Sur la base de ce code, vous pourrez reproduire l’exercice dans les autres parties de l’application. Vous pouvez aussi trouver le code complet dans la section conclusion.
Liste des auteurs
Dans cette section, nous allons créer une vue pour afficher la liste des auteurs. Créez un fichier home_page.dart
dans le dossier views
et ajoutez le code suivant :
import 'package:bibliotheca/api/auteur_api_ctl.dart';
import 'package:bibliotheca/models/auteur.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late Future<List<Auteur>> _auteurs;
@override
void initState() {
super.initState();
_auteurs = _fetchAuteurs();
}
Future<List<Auteur>> _fetchAuteurs() async {
var result = await AuteurApiCtl.getAll();
if (result.status) {
return result.data ?? [];
} else {
throw Exception('Failed to load auteurs');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Liste des Auteurs'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const CreateAuteurPage()),
).then((value) {
if (value == true) {
setState(() {
_auteurs = _fetchAuteurs();
});
}
});
},
),
],
),
body: FutureBuilder<List<Auteur>>(
future: _auteurs,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('No authors found'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
var auteur = snapshot.data![index];
return ListTile(
title: Text('${auteur.nom} ${auteur.prenoms}'),
subtitle: Text(auteur.email ?? ''),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditAuteurPage(auteur: auteur),
),
).then((value) {
if (value == true) {
setState(() {
_auteurs = _fetchAuteurs();
});
}
});
},
);
},
);
}
},
),
);
}
}
Création des auteurs
Créez un fichier create_auteur_page.dart
dans le dossier views
et ajoutez le code suivant pour la création d’un auteur :
import 'package:bibliotheca/api/auteur_api_ctl.dart';
import 'package:bibliotheca/models/auteur.dart';
import 'package:flutter/material.dart';
class CreateAuteurPage extends StatefulWidget {
const CreateAuteurPage({Key? key}) : super(key: key);
@override
_CreateAuteurPageState createState() => _CreateAuteurPageState();
}
class _CreateAuteurPageState extends State<CreateAuteurPage> {
final _formKey = GlobalKey<FormState>();
final _nomController = TextEditingController();
final _prenomsController = TextEditingController();
final _emailController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Créer un Auteur'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nomController,
decoration: const InputDecoration(labelText: 'Nom'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un nom';
}
return null;
},
),
TextFormField(
controller: _prenomsController,
decoration: const InputDecoration(labelText: 'Prénoms'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer des prénoms';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un email';
}
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
var auteur = Auteur(
nom: _nomController.text,
prenoms: _prenomsController.text,
email: _emailController.text,
);
AuteurApiCtl.create(auteur).then((result) {
if (result.status) {
Navigator.pop(context, true);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(result.message ?? 'Échec de la création')),
);
}
});
}
},
child: const Text('Créer'),
),
],
),
),
),
);
}
}
Modification des auteurs
Créez un fichier edit_auteur_page.dart
dans le dossier views
et ajoutez le code suivant pour la modification d’un auteur :
import 'package:bibliotheca/api/auteur_api_ctl.dart';
import 'package:bibliotheca/models/auteur.dart';
import 'package:flutter/material.dart';
class EditAuteurPage extends StatefulWidget {
final Auteur auteur;
const EditAuteurPage({Key? key, required this.auteur}) : super(key: key);
@override
_EditAuteurPageState createState() => _EditAuteurPageState();
}
class _EditAuteurPageState extends State<EditAuteurPage> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _nomController;
late TextEditingController _prenomsController;
late TextEditingController _emailController;
@override
void initState() {
super.initState();
_nomController = TextEditingController(text: widget.auteur.nom);
_prenomsController = TextEditingController(text: widget.auteur.prenoms);
_emailController = TextEditingController(text: widget.auteur.email);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Modifier un Auteur'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nomController,
decoration: const InputDecoration(labelText: 'Nom'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un nom';
}
return null;
},
),
TextFormField(
controller: _prenomsController,
decoration: const InputDecoration(labelText: 'Prénoms'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer des prénoms';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un email';
}
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
var auteur = Auteur(
id: widget.auteur.id,
nom: _nomController.text,
prenoms: _prenomsController.text,
email: _emailController.text,
);
AuteurApiCtl.update(auteur).then((result) {
if (result.status) {
Navigator.pop(context, true);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(result.message ?? 'Échec de la modification')),
);
}
});
}
},
child: const Text('Modifier'),
),
],
),
),
),
);
}
}
Conclusion
Ces sections montrent comment créer des vues pour lister, créer et modifier des auteurs en utilisant les APIs dans Flutter. Vous pouvez adapter ces exemples pour d’autres entités de votre application. Vous pouvez également consulter le code complet et adapter les exemples en fonction de vos besoins spécifiques.