
-
Objectifs du cours
- À la fin de ce cours, vous serez capable de :
- expliquer ce qu'est SQLite et pourquoi l'utiliser dans une application mobile ;
- installer DB Browser for SQLite et l'utiliser pour créer, remplir et interroger une base à la main ;
- ajouter les packages
sqfliteetpathà un projet Flutter ; - écrire une classe modèle avec ses méthodes
toMap()etfromMap(), en respectant la null safety de Dart ; - créer une classe
DatabaseService(patron Singleton) qui ouvre la base et crée les tables ; - insérer, lire, modifier et supprimer des données depuis Flutter ;
- ouvrir dans DB Browser la base créée par l'application pour vérifier son contenu.
- Prérequis : les bases de Dart (classes,
Future,async/await) et la leçon Flutter – Concepts de base de données. - Durée estimée : 3 heures.
- À la fin de ce cours, vous serez capable de :
-
Présentation de SQLite
- SQLite est une base de données relationnelle libre et très légère. Contrairement à MySQL, elle ne nécessite aucun serveur: toute la base (tables, données, index) est enregistrée dans un seul fichier (par exemple
people.db) stocké sur l'appareil. - Elle est intégrée à Android et à iOS : c'est la solution classique pour conserver des données structurées hors connexion (liste de courses, carnet de notes, panier, historique…) et les retrouver après la fermeture de l'application.
- On la manipule avec le langage SQL:
CREATE TABLE,INSERT,SELECT,UPDATE,DELETE. - Dans Flutter, on utilise le package
sqflite. Dans ce cours, nous construisons pas à pas la gestion d'une liste de personnes (Person), puis vous réaliserez la même chose pour des étudiants (Student).
- SQLite est une base de données relationnelle libre et très légère. Contrairement à MySQL, elle ne nécessite aucun serveur: toute la base (tables, données, index) est enregistrée dans un seul fichier (par exemple
| Plateforme | SQLite avec sqflite |
|---|---|
| Android, iOS, macOS | Fonctionne directement. |
| Windows, Linux | Fonctionne avec le package complémentaire sqflite_common_ffi (voir la section 5). |
| Web (Chrome) | Ne fonctionne pas. Ne lancez pas ces exemples dans Chrome. |
-
Rappel : classes et objets en Dart
- Une classe Dart ressemble beaucoup à une classe Java :
- déclaration avec le mot-clé
class; - attributs typés :
String,int,double,bool… souvent déclarésfinals'ils ne changent plus ; - constructeur portant le nom de la classe, avec la forme courte
this.attributpour affecter les paramètres ; - instanciation en appelant le constructeur. Le mot-clé
newest facultatif en Dart : on ne l'écrit plus.
- déclaration avec le mot-clé
- Une classe Dart ressemble beaucoup à une classe Java :

- Exemple : la classe
Personnepossède deux attributs,name(String) etage(int), initialisés par le constructeur.
// Définition de la classe Personne
class Personne {
// Attributs de la classe
final String name;
final int age;
// Constructeur : affecte directement les paramètres aux attributs
Personne(this.name, this.age);
}
void main() {
final pers = Personne('Mohamed', 23); // pas besoin de new
print('${pers.name} est âgé de ${pers.age} ans !');
// Affiche : Mohamed est âgé de 23 ans !
}
-
Installer et découvrir DB Browser for SQLite
- DB Browser for SQLite est un logiciel gratuit qui permet d'ouvrir un fichier
.db, de voir ses tables comme dans un tableur et d'exécuter des requêtes SQL. Il vous servira tout au long du chapitre à tester vos requêtes avant de les écrire en Dart, puis à vérifier ce que votre application a réellement enregistré. -
4.1. Installation
- Aller sur sqlitebrowser.org/dl.
- Télécharger la version adaptée à votre système :
- Windows : Standard installer for 64-bit Windows, puis lancer l'installateur et garder les options par défaut. Sur un poste sans droits administrateur (salle de cours), prendre la version PortableApp: elle fonctionne sans installation ;
- macOS : le fichier
.dmg, puis glisser l'application dans le dossier Applications (oubrew install --cask db-browser-for-sqlite) ; - Linux (Debian / Ubuntu) :
sudo apt install sqlitebrowser.
- Lancer DB Browser for SQLite. Pour l'avoir en français : Edit → Preferences → General → Language, choisir Français, puis relancer le logiciel.
-
4.2. Les quatre onglets à connaître
- DB Browser for SQLite est un logiciel gratuit qui permet d'ouvrir un fichier
| Onglet (français) | Onglet (anglais) | À quoi il sert |
|---|---|---|
| Structure de la base de données | Database Structure | Voir les tables, leurs colonnes et leur instruction CREATE TABLE. |
| Parcourir les données | Browse Data | Voir et modifier les lignes d'une table, comme dans un tableur. |
| Éditer les pragmas | Edit Pragmas | Réglages de la base, dont la version du schéma (User Version). |
| Exécuter le SQL | Execute SQL | Écrire et tester des requêtes SQL. |
Important : DB Browser n'enregistre rien dans le fichier tant que vous n'avez pas cliqué sur Écrire les modifications (Ctrl + S). Le bouton Annuler les modifications revient au dernier enregistrement : pratique pour tester une requête sans risque.
-
4.3. Premier essai : créer la base à la main
- Cliquer sur Nouvelle base de données et enregistrer le fichier sous le nom
essai.db. Dans la fenêtre « Éditer la définition de la table » qui s'ouvre, cliquer sur Annuler: on va créer la table en SQL. - Onglet Exécuter le SQL: coller le script ci-dessous et l'exécuter avec le bouton ▶ (ou Ctrl + Entrée).
- Cliquer sur Écrire les modifications, puis afficher la table dans l'onglet Parcourir les données.
- Cliquer sur Nouvelle base de données et enregistrer le fichier sous le nom
CREATE TABLE persons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
);
INSERT INTO persons (name, age) VALUES ('Mohamed', 23), ('Sarra', 21), ('Youssef', 25);
SELECT * FROM persons WHERE age > 22 ORDER BY name;
Point de contrôle : la requête
SELECT renvoie 2 lignes (Mohamed et Youssef) et la table persons apparaît dans l'onglet Structure de la base de données. C'est exactement cette table que l'application Flutter créera à la section 7.-
Ajouter les packages SQLite au projet Flutter
-
Étape 1 : ajouter
sqfliteetpath- Dans le terminal, à la racine du projet, exécuter :
-
flutter pub add sqflite path
Cette commande ajoute les deux packages dans pubspec.yaml, avec leur dernière version, puis exécute automatiquement flutter pub get.
# pubspec.yaml (extrait, versions indicatives)
dependencies:
flutter:
sdk: flutter
sqflite: ^2.4.2
path: ^1.9.1
| Package | Rôle |
|---|---|
sqflite |
Ouvre la base SQLite et exécute les requêtes (insert, query, update, delete…). Toutes ses méthodes sont asynchrones. |
path |
Fournit join() pour construire le chemin du fichier .db correctement sur tous les systèmes. |
path_provider (facultatif) |
Donne d'autres dossiers de l'appareil (documents, cache). Il n'est pas nécessaire pour SQLite : sqflite fournit déjà getDatabasesPath(). |
- Pour mettre à jour plus tard tous les packages vers leur dernière version majeure :
flutter pub upgrade --major-versions
-
Étape 2 (Windows et Linux uniquement) : activer SQLite sur le bureau
- Si vous lancez l'application en mode bureau (
flutter run -d windows), ajoutezsqflite_common_ffiet initialisez-le dansmain():
- Si vous lancez l'application en mode bureau (
// flutter pub add sqflite_common_ffi
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
if (!kIsWeb && (Platform.isWindows || Platform.isLinux)) {
sqfliteFfiInit();
databaseFactory = databaseFactoryFfi;
}
runApp(const MyApp());
}
-
Étape 3 : importer les bibliothèques dans le fichier Dart
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';donne accès à la fonctionjoin(), qui assemble un dossier et un nom de fichier avec le bon séparateur (/ou\).import 'package:sqflite/sqflite.dart';donne accès àopenDatabase(),getDatabasesPath(), à la classeDatabaseet à toutes les méthodes de requête.- Il n'est plus nécessaire d'importer
dart:async: les typesFutureetStreamsont disponibles par défaut en Dart.
Créer une classe modèle (model class)
- Une classe modèle représente une ligne d'une table : chaque attribut de la classe correspond à une colonne de la table. Elle sert d'intermédiaire entre vos objets Dart et la base.
- Pour créer une classe modèle :
- identifier les colonnes de la table et leur type ;
- créer la classe avec un attribut par colonne et un constructeur ;
- ajouter
toMap()pour transformer un objet enMapavant de l'enregistrer ; - ajouter
fromMap()pour recréer un objet à partir d'une ligne lue dans la base.
- Pourquoi des
Map? Parce quesqflitene connaît pas vos classes : il reçoit et renvoie chaque ligne sous la forme d'uneMap<String, dynamic>dont les clés sont les noms des colonnes.

-
6.1. La méthode
toMap(): de l'objet vers la base- Elle convertit l'objet en
Map: chaque attribut devient une paire nom de colonne → valeur. C'est cetteMapque l'on passe àdb.insert()oudb.update().
- Elle convertit l'objet en
-
6.2. Le constructeur
fromMap(): de la base vers l'objet- Il fait l'inverse : il lit les valeurs de la
Maprenvoyée pardb.query()et crée un objet. On l'écrit en général avec un constructeurfactory(voir la leçon Comprendre le concept du mot-clé « factory »).
- Il fait l'inverse : il lit les valeurs de la
-
6.3. Exemple complet : la classe
Person
class Person {
final int? id; // null tant que la personne n'est pas enregistrée (AUTOINCREMENT)
final String name; // colonne name
final int age; // colonne age
const Person({this.id, required this.name, required this.age});
// Objet -> Map : utilisé pour insert() et update()
Map<String, dynamic> toMap() {
return {
if (id != null) 'id': id, // on laisse SQLite générer l'id d'une nouvelle personne
'name': name,
'age': age,
};
}
// Map -> objet : utilisé après query()
factory Person.fromMap(Map<String, dynamic> map) {
return Person(
id: map['id'] as int?,
name: map['name'] as String,
age: map['age'] as int,
);
}
@override
String toString() => 'Person(id: $id, name: $name, age: $age)';
}
void main() {
const person = Person(name: 'John', age: 30);
final personMap = person.toMap();
print('Person as Map: $personMap'); // {name: John, age: 30}
final copie = Person.fromMap({'id': 1, 'name': 'John', 'age': 30});
print('Person from Map: $copie'); // Person(id: 1, name: John, age: 30)
}
Pourquoi
int? id? Avec AUTOINCREMENT, c'est SQLite qui choisit l'identifiant au moment de l'insertion. Avant l'enregistrement, l'objet n'a donc pas encore d'id: il vaut null. C'est aussi pour cela que toMap() n'ajoute la clé id que si elle existe.Erreur fréquente : les clés de la
Map doivent être exactement les noms des colonnes. Une clé 'Name' au lieu de 'name' provoque l'erreur table persons has no column named Name. -
6.4. Le type
dynamicdansMap<String, dynamic>- Les clés sont toujours des
String(les noms des colonnes), mais les valeurs ont des types différents d'une colonne à l'autre : texte, entier, décimal…dynamicpermet de les ranger dans la mêmeMap. - Exemple :
- Les clés sont toujours des
Map<String, dynamic> person = {
'name': 'John Doe', // String
'age': 30, // int
'isEmployed': true, // bool
'height': 1.82, // double
'favorites': ['pizza', 'movies', 'coding'], // List<String>
};
Comme
dynamic désactive le contrôle des types, on écrit map['age'] as int dans fromMap(): si la valeur n'est pas du bon type, l'erreur apparaît tout de suite, au bon endroit.-
6.5. Les types que SQLite ne connaît pas
- SQLite ne stocke que des entiers (
INTEGER), des décimaux (REAL), du texte (TEXT) et des données binaires (BLOB). Les autres types Dart doivent être convertis danstoMap()etfromMap():
- SQLite ne stocke que des entiers (
| Type Dart | Colonne SQLite | Dans toMap() |
Dans fromMap() |
|---|---|---|---|
bool |
INTEGER (0 ou 1) |
'actif': actif ? 1 : 0 |
actif: map['actif'] == 1 |
DateTime |
TEXT (date ISO 8601) |
'date': date.toIso8601String() |
date: DateTime.parse(map['date'] as String) |
double |
REAL |
'prix': prix |
prix: (map['prix'] as num).toDouble() (un prix de 5 peut revenir en int) |
List, objets |
TEXT (JSON) ou une autre table |
jsonEncode(liste) |
jsonDecode(map['liste']) |
-
Activité 01 : écrire
toMap()- Soit le constructeur
fromMapsuivant :
- Soit le constructeur
factory Book.fromMap(Map map) {
return Book(
title: map['title'],
author: map['author'],
pageCount: map['pageCount'],
);
}
- Créer la classe modèle
Bookavec les attributs nécessaires et leur type. - Écrire la méthode
toMap()correspondante. - Améliorer ce
fromMap: préciser le type de laMap(Map<String, dynamic>) et convertir chaque valeur avecas. Expliquer en une phrase ce que l'on gagne.
Bonne pratique : dans
Book.fromMap(Map map), le type des clés et des valeurs n'est pas précisé : la méthode accepte n'importe quoi et les erreurs n'apparaissent qu'à l'exécution. Écrivez toujours Map<String, dynamic> dans toMap() et fromMap(): le code est plus clair et l'éditeur peut vous aider.-
Activité 02 : la classe
Product- Définir une classe modèle
Productavec les attributsid(int?),name(String),price(double) etdescription(String?). - Écrire un constructeur avec des paramètres nommés.
- Écrire
toMap()et le constructeurfactory Product.fromMap(), en convertissant correctement le prix. - Dans
main(), créer deux produits, les convertir enMap, les recréer avecfromMap()et afficher le résultat.
- Définir une classe modèle
-
Activité 03 : gérer un
boolet une date- Créer une classe
Tacheavec les attributsid(int?),titre(String),terminee(bool) etecheance(DateTime). - Écrire
toMap()etfromMap()en appliquant les conversions du tableau 6.5. - Dans DB Browser, écrire le
CREATE TABLE tachescorrespondant, insérer deux tâches à la main et vérifier comment sont stockées les colonnestermineeetecheance.
- Créer une classe
-
6.6. Un autre exemple : la classe
Movie- Pour une table
movies(identifiant, titre, année, langue), la classe modèle s'écrit sur le même principe :
- Pour une table
class Movie {
final int? id; // colonne id INTEGER PRIMARY KEY AUTOINCREMENT
final String title; // colonne title TEXT
final int year; // colonne year INTEGER
final String language; // colonne language TEXT
const Movie({this.id, required this.title, required this.year, required this.language});
Map<String, dynamic> toMap() => {
if (id != null) 'id': id,
'title': title,
'year': year,
'language': language,
};
factory Movie.fromMap(Map<String, dynamic> map) => Movie(
id: map['id'] as int?,
title: map['title'] as String,
year: map['year'] as int,
language: map['language'] as String,
);
}
fromMap ou fromJson? Vous rencontrerez aussi fromJson() et toJson(): le principe est identique. On réserve en général fromJson / toJson aux données venant d'une API ou d'un fichier JSON, et fromMap / toMap aux lignes d'une base de données. Le type de l'id doit être le même dans la classe et dans la table (ici int et INTEGER).-
Créer la base de données et les tables
- Pour utiliser SQLite, il faut ouvrir le fichier de la base avec
openDatabase(). S'il n'existe pas encore, il est créé et la fonctiononCreateest appelée pour créer les tables. On regroupe tout ce code dans une classe unique,DatabaseService, rangée danslib/services/database_service.dart. -
7.1. Une seule instance : le patron Singleton
- Un Singleton est une classe qui n'a qu'une seule instance dans toute l'application : chaque appel à
DatabaseService()renvoie le même objet, donc la même connexion à la base. - Ce n'est pas obligatoire, mais c'est la bonne pratique pour une base de données :
- gestion centralisée : tout le code d'accès à la base est au même endroit ;
- pas de connexions multiples : la base n'est ouverte qu'une fois, ce qui évite les conflits et le gaspillage de ressources ;
- maintenance facilitée : une modification de la base n'a qu'un seul point d'impact.
- Un Singleton est une classe qui n'a qu'une seule instance dans toute l'application : chaque appel à
- Pour utiliser SQLite, il faut ouvrir le fichier de la base avec
// L'unique instance, créée une seule fois
static final DatabaseService _instance = DatabaseService._internal();
// Constructeur factory : DatabaseService() renvoie toujours _instance
factory DatabaseService() => _instance;
// Constructeur privé : impossible de créer une autre instance depuis l'extérieur
DatabaseService._internal();
// La connexion à la base, créée au premier besoin
static Database? _database;
-
7.2. Ouvrir la base au premier besoin
- Le getter
databaseouvre la base au premier appel, puis renvoie toujours la même connexion. Il renvoie uneFuture, car l'ouverture d'un fichier prend du temps et ne doit pas bloquer l'affichage (voir Les fonctions asynchrones en Dart).
- Le getter
Future<Database> get database async {
_database ??= await _initDatabase(); // ouvre la base seulement si elle ne l'est pas déjà
return _database!;
}
Future<Database> _initDatabase() async {
// Dossier prévu par le système pour les bases de l'application
final path = join(await getDatabasesPath(), 'people.db');
debugPrint('Base SQLite : $path');
return openDatabase(
path,
version: 1,
onCreate: _createTables,
);
}
-
7.3. Les principaux paramètres de
openDatabase()
| Paramètre | Rôle |
|---|---|
path |
Chemin du fichier .db, construit avec join(await getDatabasesPath(), 'nom.db'). |
version |
Numéro de version du schéma. Il faut l'augmenter quand on modifie la structure des tables. |
onConfigure |
Appelé à chaque ouverture, en premier. Sert par exemple à activer les clés étrangères : PRAGMA foreign_keys = ON. |
onCreate |
Appelé une seule fois, quand le fichier n'existe pas : on y crée les tables. |
onUpgrade |
Appelé quand version augmente : on y modifie les tables sans perdre les données (ALTER TABLE). |
onDowngrade |
Appelé quand version diminue (rare). |
onOpen |
Appelé à chaque ouverture, une fois la base prête. |
readOnly |
Ouvre la base en lecture seule. |
singleInstance |
Vrai par défaut : deux appels avec le même chemin renvoient la même connexion. |
-
7.4. Créer les tables dans
onCreate
Future<void> _createTables(Database db, int version) async {
await db.execute('''
CREATE TABLE persons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
)
''');
debugPrint('onCreate : table persons créée');
}
Attention au SQL dans les chaînes Dart : un commentaire Dart (
// ...) écrit à l'intérieur de la chaîne '''...''' est envoyé tel quel à SQLite et provoque une erreur de syntaxe, tout comme une virgule après la dernière colonne. Testez toujours votre CREATE TABLE dans DB Browser avant de le copier dans le code.-
7.5. Le fichier complet
database_service.dart- Voici la classe complète pour la table
persons, avec les quatre opérations CRUD. Chaque méthode récupère la connexion avecawait database, puis utilisetoMap()oufromMap().
- Voici la classe complète pour la table
import 'package:flutter/foundation.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import '../models/person.dart';
class DatabaseService {
// ---------- Singleton ----------
static final DatabaseService _instance = DatabaseService._internal();
factory DatabaseService() => _instance;
DatabaseService._internal();
static Database? _database;
Future<Database> get database async {
_database ??= await _initDatabase();
return _database!;
}
// ---------- Ouverture et création ----------
Future<Database> _initDatabase() async {
final path = join(await getDatabasesPath(), 'people.db');
debugPrint('Base SQLite : $path');
return openDatabase(path, version: 1, onCreate: _createTables);
}
Future<void> _createTables(Database db, int version) async {
await db.execute('''
CREATE TABLE persons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
)
''');
}
// ---------- Create ----------
Future<int> addPerson(Person person) async {
final db = await database;
return db.insert('persons', person.toMap()); // renvoie l'id créé
}
// ---------- Read ----------
Future<List<Person>> getAllPersons() async {
final db = await database;
final rows = await db.query('persons', orderBy: 'name');
return rows.map((row) => Person.fromMap(row)).toList();
}
Future<Person?> getPerson(int id) async {
final db = await database;
final rows = await db.query('persons', where: 'id = ?', whereArgs: [id], limit: 1);
return rows.isEmpty ? null : Person.fromMap(rows.first);
}
// ---------- Update ----------
Future<int> updatePerson(Person person) async {
final db = await database;
return db.update('persons', person.toMap(),
where: 'id = ?', whereArgs: [person.id]); // renvoie le nombre de lignes modifiées
}
// ---------- Delete ----------
Future<int> deletePerson(int id) async {
final db = await database;
return db.delete('persons', where: 'id = ?', whereArgs: [id]);
}
}
Pourquoi
where: 'id = ?' et whereArgs? Le ? est remplacé de façon sûre par la valeur de whereArgs. Ne collez jamais une valeur directement dans le texte de la requête ("id = $id") : c'est la porte ouverte aux erreurs et aux injections SQL. Les opérations CRUD sont détaillées dans la leçon suivante, Comment gérer les opérations sur une base de données SQLite dans Flutter. -
Utiliser la base depuis
main.dart- Avant d'utiliser SQLite au démarrage, Flutter doit être initialisé : on appelle
WidgetsFlutterBinding.ensureInitialized()au début demain(). - Le petit programme suivant insère deux personnes, les relit, en modifie une, en supprime une, et affiche le résultat dans la console (Debug Console de VS Code) :
- Avant d'utiliser SQLite au démarrage, Flutter doit être initialisé : on appelle
import 'package:flutter/material.dart';
import 'models/person.dart';
import 'services/database_service.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final db = DatabaseService();
// Create
final idSarra = await db.addPerson(const Person(name: 'Sarra', age: 21));
await db.addPerson(const Person(name: 'Youssef', age: 25));
debugPrint('Sarra enregistrée avec l\'id $idSarra');
// Read
debugPrint('Toutes les personnes : ${await db.getAllPersons()}');
// Update
await db.updatePerson(Person(id: idSarra, name: 'Sarra', age: 22));
// Delete
final persons = await db.getAllPersons();
await db.deletePerson(persons.last.id!);
debugPrint('Après modification et suppression : ${await db.getAllPersons()}');
runApp(const MaterialApp(
home: Scaffold(body: Center(child: Text('Voir la console'))),
));
}
Point de contrôle : la console affiche le chemin de la base, puis les listes de personnes. Relancez l'application : les personnes de l'exécution précédente sont toujours là, la base est bien conservée sur l'appareil.
-
Vérifier la base de l'application dans DB Browser
- Le fichier
people.dbest créé sur l'appareil, au chemin affiché dans la console. Pour l'ouvrir dans DB Browser, il faut d'abord le récupérer sur l'ordinateur :
- Le fichier
| Environnement | Méthode |
|---|---|
| Bureau (Windows, Linux, macOS) | Le fichier est directement sur votre disque, au chemin affiché dans la console. Dans DB Browser : Ouvrir une base de données en lecture seule (flèche à côté du bouton « Ouvrir »). |
| Émulateur, avec Android Studio | View → Tool Windows → Device Explorer, dossier data/data/<nom.du.paquet>/databases/, clic droit sur le dossier databases → Save As pour copier tout le dossier, puis ouvrir people.db. |
Émulateur ou téléphone, avec adb |
Dans l'Invite de commandes (cmd), pas dans PowerShell, exécuter les commandes ci-dessous. |
rem Remplacer com.example.mon_projet par le nom du paquet (applicationId dans android/app/build.gradle.kts)
adb exec-out run-as com.example.mon_projet cat databases/people.db > people.db
adb exec-out run-as com.example.mon_projet cat databases/people.db-wal > people.db-wal
- Dans DB Browser, vérifier :
- onglet Structure: la table
personset ses trois colonnes, identiques à celles deessai.db(section 4.3) ; - onglet Parcourir les données: les lignes insérées par l'application, avec leur
idgénéré automatiquement ; - onglet Éditer les pragmas: la ligne User Version vaut
1, la valeur du paramètreversiondeopenDatabase().
- onglet Structure: la table
Pièges fréquents :
1) PowerShell modifie les données redirigées avec
2) Android écrit les dernières modifications dans un fichier
3) Sur émulateur, vous travaillez sur une copie: une modification faite dans DB Browser ne change pas la base de l'application.
4) Si vous versionnez votre projet avec Git, ajoutez
1) PowerShell modifie les données redirigées avec
> et produit un fichier illisible (« file is not a database ») : utilisez cmd. 2) Android écrit les dernières modifications dans un fichier
people.db-wal: copiez-le avec le .db, sinon les dernières lignes manquent.3) Sur émulateur, vous travaillez sur une copie: une modification faite dans DB Browser ne change pas la base de l'application.
4) Si vous versionnez votre projet avec Git, ajoutez
*.db et *.db-wal au fichier .gitignore. -
Erreurs fréquentes et solutions
| Message ou symptôme | Cause | Solution |
|---|---|---|
MissingPluginException ou databaseFactory not initialized |
Application lancée sur le web, ou sur Windows/Linux sans sqflite_common_ffi |
Lancer sur Android ou iOS, ou initialiser sqflite_common_ffi (section 5, étape 2). |
no such table: persons |
La base existait déjà avant l'ajout de la table : onCreate n'est pas rappelé |
Pendant le développement : désinstaller l'application. Sinon : augmenter version et créer la table dans onUpgrade. |
table persons has no column named ... |
Clé de toMap() différente du nom de la colonne |
Comparer les clés de toMap() avec l'onglet Structure de DB Browser. |
near "/": syntax error ou near ")": syntax error |
Commentaire // ou virgule en trop dans le CREATE TABLE |
Corriger le SQL et le tester d'abord dans DB Browser. |
type 'int' is not a subtype of type 'double' |
Un nombre décimal enregistré sans décimale revient en int |
Écrire (map['prix'] as num).toDouble(). |
Null check operator used on a null value sur id! |
Objet créé en Dart mais jamais relu depuis la base | Utiliser l'id renvoyé par insert(), ou relire l'objet avec getPerson(). |
UNIQUE constraint failed |
Insertion d'un id (ou d'une valeur UNIQUE) déjà présent |
Laisser l'id à null pour une nouvelle ligne, ou utiliser update(). |
-
Activité de synthèse : gérer des étudiants
- Objectif : refaire seul toute la démarche du cours pour une table
students. - Consignes :
- Dans DB Browser : créer une base
brouillon.dbet y tester leCREATE TABLE studentsavec les colonnesid(entier, clé primaire auto-incrémentée),name(texte obligatoire),age(entier),major(spécialité, texte),email(texte unique) etactif(booléen stocké en 0/1). Insérer trois étudiants et écrire une requête qui affiche les étudiants actifs triés par nom. - Classe modèle : créer
lib/models/student.dartavec la classeStudent, son constructeur,toMap(),factory Student.fromMap()ettoString(). - Service : créer
lib/services/database_service.dartsur le modèle de la section 7.5 (Singleton, baseschool.db, tablestudents) avec les méthodesaddStudent,getAllStudents,getStudent,updateStudentetdeleteStudent. - Test : dans
main.dart, insérer trois étudiants, en modifier un, en supprimer un et afficher la liste dans la console après chaque opération. - Erreur volontaire : insérer deux étudiants avec le même e-mail. Relever le message d'erreur et l'intercepter avec un
try / catchqui affiche « Cet e-mail est déjà utilisé ». - Vérification : récupérer
school.dbet l'ouvrir dans DB Browser. Faire une capture de l'onglet Parcourir les données montrant la tablestudents.
- Dans DB Browser : créer une base
- Objectif : refaire seul toute la démarche du cours pour une table