Maîtriser la Consommation d’API REST en Flutter : Guide Pratique

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 :

PHP
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
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
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) :

Dart
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 :

YAML
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 :

Dart
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 :

YAML
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 :

Dart
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 :

Dart
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 auteurs
  • create(): pour créer un auteur
  • update(): pour mettre à jour un auteur
  • delete(): 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.

Dart
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 :

Dart
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 :

Dart
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 :

Dart
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 :

Dart
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 :

Dart
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 :

Dart
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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *