Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
Flutter

Application Flutter pour consommer JSONPlaceholder API

Un cours complet pour maîtriser Application Flutter pour consommer JSONPlaceholder API.

0 chapitres 0 QCM Accès gratuit
  1. Objectif

    • Permettre à l’apprenant de maîtriser la consommation d’API REST et GraphQL dans une application Flutter afin d’interagir efficacement avec des services distants (lecture, création, mise à jour et suppression de données).
  2. Structure du projet

      lib/ ├── main.dart ├── models/ │ └── user.dart ├── services/ │ └── api_service.dart └── screens/ ├── users_screen.dart ├── user_detail_screen.dart └── add_edit_user_screen.dart
  3. Code complet

    1. Fichier pubspec.yaml
        name: jsonplaceholder_app
        description: Une application Flutter pour gérer les utilisateurs via JSONPlaceholder API
        
        publish_to: 'none'
        
        version: 1.0.0+1
        
        environment:
          sdk: '>=3.0.0 <4.0.0'
        
        dependencies:
          flutter:
            sdk: flutter
          http: ^1.1.0
          provider: ^6.0.5
        
        dev_dependencies:
          flutter_test:
            sdk: flutter
          flutter_lints: ^2.0.0
        
        flutter:
          uses-material-design: true
    2. Modèle lib/models/user.dart
        class User {
          final int? id;
          final String name;
          final String username;
          final String email;
          final String phone;
          final String website;
          final Address address;
          final Company company;
        
          User({
            this.id,
            required this.name,
            required this.username,
            required this.email,
            required this.phone,
            required this.website,
            required this.address,
            required this.company,
          });
        
          factory User.fromJson(Map json) {
            return User(
              id: json['id'],
              name: json['name'],
              username: json['username'],
              email: json['email'],
              phone: json['phone'],
              website: json['website'],
              address: Address.fromJson(json['address']),
              company: Company.fromJson(json['company']),
            );
          }
        
          Map toJson() {
            return {
              if (id != null) 'id': id,
              'name': name,
              'username': username,
              'email': email,
              'phone': phone,
              'website': website,
              'address': address.toJson(),
              'company': company.toJson(),
            };
          }
        
          User copyWith({
            int? id,
            String? name,
            String? username,
            String? email,
            String? phone,
            String? website,
            Address? address,
            Company? company,
          }) {
            return User(
              id: id ?? this.id,
              name: name ?? this.name,
              username: username ?? this.username,
              email: email ?? this.email,
              phone: phone ?? this.phone,
              website: website ?? this.website,
              address: address ?? this.address,
              company: company ?? this.company,
            );
          }
        }
        
        class Address {
          final String street;
          final String suite;
          final String city;
          final String zipcode;
          final Geo geo;
        
          Address({
            required this.street,
            required this.suite,
            required this.city,
            required this.zipcode,
            required this.geo,
          });
        
          factory Address.fromJson(Map json) {
            return Address(
              street: json['street'],
              suite: json['suite'],
              city: json['city'],
              zipcode: json['zipcode'],
              geo: Geo.fromJson(json['geo']),
            );
          }
        
          Map toJson() {
            return {
              'street': street,
              'suite': suite,
              'city': city,
              'zipcode': zipcode,
              'geo': geo.toJson(),
            };
          }
        
          Address copyWith({
            String? street,
            String? suite,
            String? city,
            String? zipcode,
            Geo? geo,
          }) {
            return Address(
              street: street ?? this.street,
              suite: suite ?? this.suite,
              city: city ?? this.city,
              zipcode: zipcode ?? this.zipcode,
              geo: geo ?? this.geo,
            );
          }
        }
        
        class Geo {
          final String lat;
          final String lng;
        
          Geo({
            required this.lat,
            required this.lng,
          });
        
          factory Geo.fromJson(Map json) {
            return Geo(
              lat: json['lat'],
              lng: json['lng'],
            );
          }
        
          Map toJson() {
            return {
              'lat': lat,
              'lng': lng,
            };
          }
        }
        
        class Company {
          final String name;
          final String catchPhrase;
          final String bs;
        
          Company({
            required this.name,
            required this.catchPhrase,
            required this.bs,
          });
        
          factory Company.fromJson(Map json) {
            return Company(
              name: json['name'],
              catchPhrase: json['catchPhrase'],
              bs: json['bs'],
            );
          }
        
          Map toJson() {
            return {
              'name': name,
              'catchPhrase': catchPhrase,
              'bs': bs,
            };
          }
        
          Company copyWith({
            String? name,
            String? catchPhrase,
            String? bs,
          }) {
            return Company(
              name: name ?? this.name,
              catchPhrase: catchPhrase ?? this.catchPhrase,
              bs: bs ?? this.bs,
            );
          }
        }
    3. Service API lib/services/api_service.dart
        import 'dart:convert';
        import 'package:http/http.dart' as http;
        import '../models/user.dart';
        
        class ApiService {
          static const String baseUrl = 'https://jsonplaceholder.typicode.com';
        
          Future> getUsers() async {
            final response = await http.get(Uri.parse('$baseUrl/users'));
            
            if (response.statusCode == 200) {
              List data = json.decode(response.body);
              return data.map((json) => User.fromJson(json)).toList();
            } else {
              throw Exception('Échec du chargement des utilisateurs');
            }
          }
        
          Future getUser(int id) async {
            final response = await http.get(Uri.parse('$baseUrl/users/$id'));
            
            if (response.statusCode == 200) {
              return User.fromJson(json.decode(response.body));
            } else {
              throw Exception('Échec du chargement de l\'utilisateur');
            }
          }
        
          Future createUser(User user) async {
            final response = await http.post(
              Uri.parse('$baseUrl/users'),
              headers: {'Content-Type': 'application/json; charset=UTF-8'},
              body: json.encode(user.toJson()),
            );
            
            if (response.statusCode == 201) {
              // JSONPlaceholder renvoie l'utilisateur créé avec un nouvel ID
              return User.fromJson(json.decode(response.body));
            } else {
              throw Exception('Échec de la création de l\'utilisateur');
            }
          }
        
          Future updateUser(User user) async {
            final response = await http.put(
              Uri.parse('$baseUrl/users/${user.id}'),
              headers: {'Content-Type': 'application/json; charset=UTF-8'},
              body: json.encode(user.toJson()),
            );
            
            if (response.statusCode == 200) {
              return User.fromJson(json.decode(response.body));
            } else {
              throw Exception('Échec de la mise à jour de l\'utilisateur');
            }
          }
        
          Future deleteUser(int id) async {
            final response = await http.delete(Uri.parse('$baseUrl/users/$id'));
            
            if (response.statusCode != 200) {
              throw Exception('Échec de la suppression de l\'utilisateur');
            }
          }
        } 
    4. Écran liste utilisateurs lib/screens/users_screen.dart
        import 'package:flutter/material.dart';
        import '../models/user.dart';
        import '../services/api_service.dart';
        import 'user_detail_screen.dart';
        import 'add_edit_user_screen.dart';
        
        class UsersScreen extends StatefulWidget {
          const UsersScreen({super.key});
        
          @override
          State createState() => _UsersScreenState();
        }
        
        class _UsersScreenState extends State {
          final ApiService _apiService = ApiService();
          late Future> _futureUsers;
          final List _users = [];
        
          @override
          void initState() {
            super.initState();
            _futureUsers = _apiService.getUsers();
          }
        
          void _refreshUsers() {
            setState(() {
              _futureUsers = _apiService.getUsers();
            });
          }
        
          void _navigateToAddUser() async {
            final result = await Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const AddEditUserScreen()),
            );
            
            if (result == true) {
              _refreshUsers();
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Utilisateur créé avec succès')),
              );
            }
          }
        
          void _navigateToEditUser(User user) async {
            final result = await Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => AddEditUserScreen(user: user)),
            );
            
            if (result == true) {
              _refreshUsers();
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Utilisateur modifié avec succès')),
              );
            }
          }
        
          void _deleteUser(int id) async {
            try {
              await _apiService.deleteUser(id);
              _refreshUsers();
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Utilisateur supprimé avec succès')),
              );
            } catch (e) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Erreur: ${e.toString()}')),
              );
            }
          }
        
          void _showDeleteDialog(User user) {
            showDialog(
              context: context,
              builder: (BuildContext context) {
                return AlertDialog(
                  title: const Text('Confirmer la suppression'),
                  content: Text('Êtes-vous sûr de vouloir supprimer ${user.name}?'),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.of(context).pop(),
                      child: const Text('Annuler'),
                    ),
                    TextButton(
                      onPressed: () {
                        _deleteUser(user.id!);
                        Navigator.of(context).pop();
                      },
                      child: const Text('Supprimer', style: TextStyle(color: Colors.red)),
                    ),
                  ],
                );
              },
            );
          }
        
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: const Text('Utilisateurs JSONPlaceholder'),
                actions: [
                  IconButton(
                    icon: const Icon(Icons.refresh),
                    onPressed: _refreshUsers,
                  ),
                ],
              ),
              body: FutureBuilder>(
                future: _futureUsers,
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return const Center(child: CircularProgressIndicator());
                  } else if (snapshot.hasError) {
                    return Center(child: Text('Erreur: ${snapshot.error}'));
                  } else if (snapshot.hasData) {
                    final users = snapshot.data!;
                    return ListView.builder(
                      itemCount: users.length,
                      itemBuilder: (context, index) {
                        final user = users[index];
                        return Card(
                          margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
                          child: ListTile(
                            leading: CircleAvatar(
                              child: Text(user.name[0]),
                            ),
                            title: Text(user.name),
                            subtitle: Text(user.email),
                            onTap: () {
                              Navigator.push(
                                context,
                                MaterialPageRoute(
                                  builder: (context) => UserDetailScreen(user: user),
                                ),
                              );
                            },
                            trailing: Row(
                              mainAxisSize: MainAxisSize.min,
                              children: [
                                IconButton(
                                  icon: const Icon(Icons.edit, color: Colors.blue),
                                  onPressed: () => _navigateToEditUser(user),
                                ),
                                IconButton(
                                  icon: const Icon(Icons.delete, color: Colors.red),
                                  onPressed: () => _showDeleteDialog(user),
                                ),
                              ],
                            ),
                          ),
                        );
                      },
                    );
                  } else {
                    return const Center(child: Text('Aucun utilisateur trouvé'));
                  }
                },
              ),
              floatingActionButton: FloatingActionButton(
                onPressed: _navigateToAddUser,
                child: const Icon(Icons.add),
              ),
            );
          }
        }
    5. Écran ajout/modification utilisateur lib/screens/add_edit_user_screen.dart
        import 'package:flutter/material.dart';
        import '../models/user.dart';
        import '../services/api_service.dart';
        
        class AddEditUserScreen extends StatefulWidget {
          final User? user;
        
          const AddEditUserScreen({super.key, this.user});
        
          @override
          State createState() => _AddEditUserScreenState();
        }
        
        class _AddEditUserScreenState extends State {
          final _formKey = GlobalKey();
          final ApiService _apiService = ApiService();
        
          late TextEditingController _nameController;
          late TextEditingController _usernameController;
          late TextEditingController _emailController;
          late TextEditingController _phoneController;
          late TextEditingController _websiteController;
          late TextEditingController _streetController;
          late TextEditingController _suiteController;
          late TextEditingController _cityController;
          late TextEditingController _zipcodeController;
          late TextEditingController _latController;
          late TextEditingController _lngController;
          late TextEditingController _companyNameController;
          late TextEditingController _catchPhraseController;
          late TextEditingController _bsController;
        
          @override
          void initState() {
            super.initState();
            
            final user = widget.user;
            _nameController = TextEditingController(text: user?.name ?? '');
            _usernameController = TextEditingController(text: user?.username ?? '');
            _emailController = TextEditingController(text: user?.email ?? '');
            _phoneController = TextEditingController(text: user?.phone ?? '');
            _websiteController = TextEditingController(text: user?.website ?? '');
            _streetController = TextEditingController(text: user?.address.street ?? '');
            _suiteController = TextEditingController(text: user?.address.suite ?? '');
            _cityController = TextEditingController(text: user?.address.city ?? '');
            _zipcodeController = TextEditingController(text: user?.address.zipcode ?? '');
            _latController = TextEditingController(text: user?.address.geo.lat ?? '');
            _lngController = TextEditingController(text: user?.address.geo.lng ?? '');
            _companyNameController = TextEditingController(text: user?.company.name ?? '');
            _catchPhraseController = TextEditingController(text: user?.company.catchPhrase ?? '');
            _bsController = TextEditingController(text: user?.company.bs ?? '');
          }
        
          @override
          void dispose() {
            _nameController.dispose();
            _usernameController.dispose();
            _emailController.dispose();
            _phoneController.dispose();
            _websiteController.dispose();
            _streetController.dispose();
            _suiteController.dispose();
            _cityController.dispose();
            _zipcodeController.dispose();
            _latController.dispose();
            _lngController.dispose();
            _companyNameController.dispose();
            _catchPhraseController.dispose();
            _bsController.dispose();
            super.dispose();
          }
        
          Future _saveUser() async {
            if (_formKey.currentState!.validate()) {
              try {
                final address = Address(
                  street: _streetController.text,
                  suite: _suiteController.text,
                  city: _cityController.text,
                  zipcode: _zipcodeController.text,
                  geo: Geo(
                    lat: _latController.text,
                    lng: _lngController.text,
                  ),
                );
        
                final company = Company(
                  name: _companyNameController.text,
                  catchPhrase: _catchPhraseController.text,
                  bs: _bsController.text,
                );
        
                final user = User(
                  id: widget.user?.id,
                  name: _nameController.text,
                  username: _usernameController.text,
                  email: _emailController.text,
                  phone: _phoneController.text,
                  website: _websiteController.text,
                  address: address,
                  company: company,
                );
        
                if (widget.user == null) {
                  await _apiService.createUser(user);
                } else {
                  await _apiService.updateUser(user);
                }
        
                Navigator.pop(context, true);
              } catch (e) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Erreur: ${e.toString()}')),
                );
              }
            }
          }
        
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text(widget.user == null ? 'Ajouter un utilisateur' : 'Modifier l\'utilisateur'),
              ),
              body: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Form(
                  key: _formKey,
                  child: ListView(
                    children: [
                      const Text('Informations personnelles', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      TextFormField(
                        controller: _nameController,
                        decoration: const InputDecoration(labelText: 'Nom'),
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Veuillez entrer un nom';
                          }
                          return null;
                        },
                      ),
                      TextFormField(
                        controller: _usernameController,
                        decoration: const InputDecoration(labelText: 'Nom d\'utilisateur'),
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Veuillez entrer un nom d\'utilisateur';
                          }
                          return null;
                        },
                      ),
                      TextFormField(
                        controller: _emailController,
                        decoration: const InputDecoration(labelText: 'Email'),
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Veuillez entrer un email';
                          }
                          if (!value.contains('@')) {
                            return 'Veuillez entrer un email valide';
                          }
                          return null;
                        },
                      ),
                      TextFormField(
                        controller: _phoneController,
                        decoration: const InputDecoration(labelText: 'Téléphone'),
                      ),
                      TextFormField(
                        controller: _websiteController,
                        decoration: const InputDecoration(labelText: 'Site web'),
                      ),
                      
                      const SizedBox(height: 20),
                      const Text('Adresse', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      TextFormField(
                        controller: _streetController,
                        decoration: const InputDecoration(labelText: 'Rue'),
                      ),
                      TextFormField(
                        controller: _suiteController,
                        decoration: const InputDecoration(labelText: 'Appartement/Suite'),
                      ),
                      TextFormField(
                        controller: _cityController,
                        decoration: const InputDecoration(labelText: 'Ville'),
                      ),
                      TextFormField(
                        controller: _zipcodeController,
                        decoration: const InputDecoration(labelText: 'Code postal'),
                      ),
                      TextFormField(
                        controller: _latController,
                        decoration: const InputDecoration(labelText: 'Latitude'),
                      ),
                      TextFormField(
                        controller: _lngController,
                        decoration: const InputDecoration(labelText: 'Longitude'),
                      ),
                      
                      const SizedBox(height: 20),
                      const Text('Entreprise', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      TextFormField(
                        controller: _companyNameController,
                        decoration: const InputDecoration(labelText: 'Nom de l\'entreprise'),
                      ),
                      TextFormField(
                        controller: _catchPhraseController,
                        decoration: const InputDecoration(labelText: 'Slogan'),
                      ),
                      TextFormField(
                        controller: _bsController,
                        decoration: const InputDecoration(labelText: 'BS'),
                      ),
                      
                      const SizedBox(height: 20),
                      ElevatedButton(
                        onPressed: _saveUser,
                        child: Text(widget.user == null ? 'Créer' : 'Mettre à jour'),
                      ),
                    ],
                  ),
                ),
              ),
            );
          }
        }
    6. Écran détails utilisateur lib/screens/user_detail_screen.dart
        import 'package:flutter/material.dart';
        import '../models/user.dart';
        
        class UserDetailScreen extends StatelessWidget {
          final User user;
        
          const UserDetailScreen({super.key, required this.user});
        
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text(user.name),
              ),
              body: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Card(
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text('Informations personnelles', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                            const SizedBox(height: 10),
                            Text('Nom: ${user.name}'),
                            Text('Nom d\'utilisateur: ${user.username}'),
                            Text('Email: ${user.email}'),
                            Text('Téléphone: ${user.phone}'),
                            Text('Site web: ${user.website}'),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(height: 16),
                    Card(
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text('Adresse', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                            const SizedBox(height: 10),
                            Text('Rue: ${user.address.street}'),
                            Text('Appartement/Suite: ${user.address.suite}'),
                            Text('Ville: ${user.address.city}'),
                            Text('Code postal: ${user.address.zipcode}'),
                            Text('Coordonnées: ${user.address.geo.lat}, ${user.address.geo.lng}'),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(height: 16),
                    Card(
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text('Entreprise', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                            const SizedBox(height: 10),
                            Text('Nom: ${user.company.name}'),
                            Text('Slogan: ${user.company.catchPhrase}'),
                            Text('BS: ${user.company.bs}'),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          }
        }
    7. Fichier principal lib/main.dart
        import 'package:flutter/material.dart';
        import 'screens/users_screen.dart';
        
        void main() {
          runApp(const MyApp());
        }
        
        class MyApp extends StatelessWidget {
          const MyApp({super.key});
        
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              title: 'JSONPlaceholder App',
              theme: ThemeData(
                primarySwatch: Colors.blue,
                useMaterial3: true,
              ),
              home: const UsersScreen(),
              debugShowCheckedModeBanner: false,
            );
          }
        }