Atelier 5 : Gestionnaire de Contacts avec Persistance (SQLite + Riverpod)
Vue d'ensemble
Cet atelier reprend le Gestionnaire de Contacts de l'Atelier 2, mais avec persistance des données en base de données locale (SQLite) et gestion d'état avec Riverpod.
Objectifs
- Implémenter SQLite pour persister les contacts
- Utiliser Riverpod pour séparer logique et UI
- Appliquer l'architecture montrée dans le Cours 5.3
- Gérer les opérations CRUD (Create, Read, Update, Delete)
- Afficher les contacts sauvegardés même après fermeture de l'app
Stack technologique
- SQLite : Stockage persistent des données
- Riverpod : Gestion d'état
- flutter_riverpod : Intégration Riverpod dans Flutter
Structure du projet
Créez l'architecture suivante :
lib/
├── models/
│ └── contact.dart # Modèle Contact
├── services/
│ └── database_service.dart # Service accès BD
├── providers/
│ └── contact_providers.dart # Providers Riverpod
├── screens/
│ └── contacts_list_screen.dart # UI principale
└── main.dart # Point d'entrée
Partie 1 : Modèle de données
Créez un fichier lib/models/contact.dart avec une classe Contact qui contient :
Propriétés
int? id: Clé primaire (nullable pour les nouveaux contacts)String firstName: PrénomString lastName: NomString email: Adresse emailString phone: Numéro de téléphone
Getters
String fullName: Retourne le nom completString initials: Retourne les initiales (premières lettres du prénom et nom)
Méthodes
factory Contact.fromMap(Map<String, dynamic> map): Convertir depuis la base de donnéesMap<String, dynamic> toMap(): Convertir vers la base de donnéesContact copyWith({...}): Créer une copie modifiée (immuabilité)
Partie 2 : Service d'accès à la Base de Données
Créez un fichier lib/services/database_service.dart avec une classe DatabaseService :
Pattern Singleton
- Instance unique pour toute l'application
- Constructeur privé
Propriétés
late Database _db: Instance de la base de données
Méthodes
Future<void> init(): Initialiser la BD avec tablecontacts- Colonnes : id, firstName, lastName, email, phone
Future<List<Contact>> getContacts(): Récupérer tous les contactsFuture<int> addContact(Contact contact): Ajouter un contactFuture<void> updateContact(Contact contact): Modifier un contactFuture<void> deleteContact(int id): Supprimer un contact
Partie 3 : Providers (Logique métier)
Créez un fichier lib/providers/contact_providers.dart avec 3 providers :
Provider 1 : Service BD
dbProvider: Provider simple qui retourne l'instance deDatabaseService
Provider 2 : Liste des contacts
contactsProvider: FutureProvider qui récupère la liste des contacts depuis la BD
Provider 3 : Actions sur contacts
contactActionsProvider: StateNotifierProvider qui gère les actions- Classe
ContactActions extends StateNotifier<AsyncValue<void>>- Méthode
addContact(firstName, lastName, email, phone) - Méthode
updateContact(Contact contact) - Méthode
deleteContact(int id) - Chaque méthode doit appeler
ref.refresh(contactsProvider)après modification
- Méthode
Partie 4 : Interface utilisateur
Créez un fichier lib/screens/contacts_list_screen.dart :
Widget principal
ContactsListScreen extends ConsumerWidget- Utilise
ref.watch(contactsProvider)pour afficher la liste - Utilise
ref.read(contactActionsProvider.notifier)pour les actions
Fonctionnalités UI
- Affichage de la liste des contacts avec
ListView.builder - Gestion des 3 états avec
.when(): loading, error, data - FloatingActionButton pour ajouter un contact
- Boutons pour éditer/supprimer chaque contact
- Dialog pour ajouter un nouveau contact (4 TextFields)
- Dialog pour modifier un contact existant (4 TextFields pré-remplis)
Widget ContactTile
- Affiche le CircleAvatar avec initiales
- Affiche nom complet et email
- Boutons éditer et supprimer
Partie 5 : Point d'entrée
Créez/modifiez le fichier lib/main.dart :
Fonction main()
- Initialiser Flutter avec
WidgetsFlutterBinding.ensureInitialized() - Initialiser la base de données avec
await DatabaseService().init() - Wrapper l'app avec
ProviderScope - Lancer l'application
Widget MyApp
- MaterialApp avec theme
- Home :
ContactsListScreen
À faire
Niveau 1 : Compléter la base
- Créer les 5 fichiers dans la structure proposée
- Implémenter le modèle
Contactavec toutes les méthodes - Implémenter
DatabaseServiceavec le pattern Singleton et toutes les opérations CRUD - Implémenter les 3 providers Riverpod
- Créer l'UI complète avec dialogs pour add/edit
- Tester l'ajout, la modification et la suppression
- Vérifier que les données persistent après fermeture de l'app
Niveau 2 : Améliorations
- Ajouter une recherche par nom ou email
- Ajouter une validation des emails et téléphones avant sauvegarde
- Implémenter un tri (par nom, par date d'ajout)
- Ajouter une confirmation (AlertDialog) avant suppression
- Ajouter une photo de profil (optionnel, stocker le path)
Niveau 3 : Avancé
- Implémenter un undo/redo (garder historique des modifications)
- Ajouter la synchronisation avec une API REST backend
- Implémenter l'export des contacts en CSV
- Ajouter un flag favori pour certains contacts
- Implémenter des groupes de contacts (table séparée avec relation)
Concepts à retenir
| Concept | Rôle |
|---|---|
| Modèle | Structure des données (Contact) |
| Service | Encapsule accès BD (DatabaseService) |
| Provider | Source de données (contactsProvider) |
| StateNotifier | Gère les actions (ContactActions) |
| UI (ConsumerWidget) | Affiche et appelle les actions |
Schéma de la Base de Données
Table contacts :
id: INTEGER PRIMARY KEY AUTOINCREMENTfirstName: TEXT NOT NULLlastName: TEXT NOT NULLemail: TEXT NOT NULLphone: TEXT NOT NULL
Dependencies à ajouter
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.4.0
riverpod_annotation: ^2.1.0
sqflite: ^2.2.8+4
path: ^1.8.3
dev_dependencies:
build_runner: ^2.4.6
riverpod_generator: ^2.3.6
Conseils
- Référez-vous au Cours 5.3 (Riverpod) pour l'architecture et les exemples de code
- Testez chaque partie séparément avant d'intégrer
- Utilisez
print()pour debugger les opérations BD - N'oubliez pas d'appeler
ref.refresh()après modifications - Gérez les erreurs avec
AsyncValue.guard()