Comment utiliser SQLite dans Flutter
Comment utiliser SQLite dans Flutter
-
Présentation
- SQLite dans Flutter peut être utilisé pour conserver les données dans les applications Android et iOS. Dans ce tutoriel, nous allons créer une application simple pour enregistrer des données dans des listes telles qu’une liste d’épicerie.
-
Rappel : Notions de Classes et POO
- Une classe Dart possède exactement la même structure qu’une classe Java:
- Déclaration à l’aide l’instruction
class
- Déclaration des attributs à l’aide des mots clé:
String
,int
,var
,final
… - Déclaration du constructeur sous le même nom de la classe.
- Affectation des paramètres aux attributs via l’instruction
this
. - Instanciation via la commande
new
-
Exemple:
- L’exemple définit une classe Personne avec deux propriétés :
name de type String
etage de type int
. Le constructeur de la classe initialise ces propriétés lors de la création d’une instance de la classe. -
Ajouter la dépendance
SQLite
dans Flutter - SQLite est une base de données relationnelle open source qui peut être utilisée pour stocker et manipuler des données comme ajouter, supprimer et supprimer des données.
- Il ne nécessite pas de serveur ou de code back-end et toutes les données sont enregistrées dans un fichier texte sur l’appareil.
-
Étape 1: Ajoutez-le package
sqflite
àpubspec.yaml
- Pour récupérer le package
sqflite
, il faut modifier le fichierpubspec.yaml
. - Dans le fichier
pubspec.yaml
de votre projet, ajoutez la dépendance SQLite à la section dependencies : Path_provider
permet de trouver les chemins ou emplacements couramment utilisés sur le système de fichiers.Path
est utile dans les opérations permettant la manipulation de chemins ou d’emplacements.- Ensuite, exécutez
flutter pub get
dans le terminal pour installer la dépendance. - Vous pouvez trouver les versions actuelles de ces packages ici :
-
Étape 2: Importer la bibliothèque SQLite (package
sqflite
) dans votre fichier Dart - Assurez-vous d’avoir importé les packages dans le fichier que vous souhaitez utiliser
import 'dart:async';
: Ceci importe la bibliothèque dart:async, qui fournit des fonctionnalités pour travailler avec des opérations asynchrones en Dart.- Dans le contexte de la programmation de bases de données, cela est souvent utilisé pour effectuer des opérations non bloquantes, telles que l’ouverture ou l’exécution de requêtes sur une base de données.
import 'package:path/path.dart';
: Ceci importe la fonction join de la bibliothèque path qui fait partie du framework Dart. Cette fonction est utilisée pour manipuler les chemins de fichiers de manière indépendante de la plate-forme, ce qui est utile lors de la spécification du chemin d’accès à une base de données SQLite.import 'package:sqflite/sqflite.dart';
: Ceci importe la bibliothèque sqflite, qui est une bibliothèque Dart SQLite pour Flutter et Dart. Cette bibliothèque fournit des fonctionnalités pour interagir avec les bases de données SQLite dans une application Flutter. Elle permet de créer, lire, mettre à jour et supprimer des données dans une base de données SQLite de manière asynchrone, ce qui est couramment utilisé dans le développement d’applications mobiles Flutter.
// Définition de la classe Personne
class Personne {
// Attributs de la classe
String name ;
int age ;
// constructeur de la classe
Personne(this.name , this.age);
}void main() {
var pers = new Personne("Mohamed", 23);
print("${pers.name} est agé de ${pers.age} ans !");
// Affiche: Mohamed est âgé de 23 ans !
}
dependencies:
flutter:
sdk: flutter
path_provider: ^2.1.0
path: ^1.6.4
sqflite: ^2.3.0
ou tapez:
flutter pub upgrade --major-versions
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
Créer une classe modèle (model class)
- Créer une classe modèle (model class) pour représenter une table dans une base de données consiste à définir une structure de données qui correspond aux colonnes de la table.
- Dans le contexte d’une base de données SQLite, cela signifie créer une classe Dart qui représente une entité spécifique de votre application (par exemple, un objet Movie ou Film en francais dans le scénario de gestion de films).
- Voici comment vous pouvez créer une classe modèle pour représenter une table dans Flutter avec SQLite :
-
Créez la classe modèle :
- La création d’une classe modèle pour une base de données SQLite dans Flutter implique plusieurs étapes. Voici les étapes générales pour créer une classe modèle :
- Définir les propriétés de la classe : Identifiez les différents attributs ou champs que vous souhaitez stocker dans votre base de données SQLite. Chaque attribut correspondra à une colonne dans votre table SQLite.
- Créer la classe modèle : Créez une classe Dart pour représenter votre modèle de données. Cette classe devrait contenir les attributs définis à l’étape précédente.
- Implémenter les méthodes
toMap
etfromMap
: Ajoutez des méthodes à votre classe pour convertir les objets de votre modèle en Map<String, dynamic> (et vice versa). La méthode toMap convertit un objet modèle en un Map et la méthode fromMap fait l’inverse. - Gérer les opérations CRUD : En fonction de vos besoins, vous pouvez également ajouter des méthodes pour effectuer des opérations CRUD (Create, Read, Update, Delete) sur vos données. Ces méthodes interagiront avec votre base de données SQLite à l’aide du package sqflite.
-
Créez les Méthodes
toMap
() etfromMap()
: - Les méthodes
toMap()
etfromMap()
sont utilisées dans une classe modèle pour faciliter l’interaction avec une base de données SQLite en Dart. -
toMap() :
- Cette méthode convertit un objet de la classe en un objet Map. Chaque attribut de la classe est associé à une clé dans le Map, ce qui permet d’insérer facilement les données dans la base de données.
- Dans cet exemple, la méthode
toMap()
de la classe Person convertit un objet Person en un Map contenant les clés ‘name’ et ‘age’, correspondant aux attributs de la classe. -
fromMap() :
- Cette méthode crée un nouvel objet de la classe à partir d’un Map récupéré de la base de données. Elle inverse le processus de
toMap()
en utilisant les valeurs du Map pour initialiser les attributs de la classe. - Exemple :
- Dans cet exemple, la méthode
fromMap()
de la classe Person prend un Map en argument et utilise les valeurs associées aux clés ‘name’ et ‘age’ pour initialiser les attributs de la classe. -
Le type dynamic dans Map<String, dynamic>
- Le type dynamic dans
Map<String, dynamic>
signifie que les valeurs associées aux clés peuvent être de n’importe quel type dynamique en Dart. - Cela permet à un Map d’accepter des valeurs de différents types sans imposer de contrainte de type spécifique.
- En utilisant dynamic, vous pouvez stocker des valeurs de types variés dans le même Map, ce qui est souvent utile lors de la manipulation de données hétérogènes, telles que celles récupérées depuis une base de données où les types peuvent varier d’une entrée à une autre.
- Exemple:
- Dans cet exemple, le Map person contient des valeurs de types différents :
- En spécifiant dynamic, vous pouvez stocker ces valeurs hétérogènes dans le même Map sans problème.
- Acitivité 01
- Soit la méthode fromMap suivante :
- Ecrire la méthode toMap correspondante pour la classe Book et de créer la classe modèle avec les propriétés nécessaires.
- Créez une classe modèle Book avec les propriétés nécessaire.
- Dans l’exemple
Book.fromMap(Map map)
, letype
des clés et des valeurs de la Map n’est pas précisé. Cela signifie que la méthode fromMap peut accepter n’importe quel type de clés et de valeurs dans la Map. Bien que cela soit possible, il est généralement préférable de spécifier explicitement le type des clés et des valeurs pour plus de clarté et de sécurité. - Ainsi, pour des raisons de lisibilité et de bonnes pratiques, il est recommandé de spécifier le type des clés et des valeurs de la
Map
dans les méthodesfromMap
ettoMap
. Cela permet de mieux définir les attentes de la méthode et de faciliter la compréhension du code par les autres développeurs. - Acitivité 02
- Définissez une classe modèle appelée Product.
- Ajoutez des propriétés à la classe Product, notamment name, price, description, etc.
- Assurez-vous que chaque propriété a un type de données approprié.
- Implémentez un constructeur pour la classe Product qui permet d’initialiser les propriétés.
- Écrivez une méthode toMap() dans la classe Product qui convertit les objets Product en un format de Map.
- Écrivez une méthode fromMap() dans la classe Product qui crée un objet Product à partir d’un Map donné.
- Testez votre implémentation en créant des instances de la classe Product, en les sérialisant en Map à l’aide de la méthode toMap(), puis en les désérialisant à l’aide de la méthode fromMap().
- Acitivité 03
Exemple :
class Person {
String name;
int age;
Map<String, dynamic> toMap() {
return {
'name': name,
'age': age,
};
}
}
class Person {
String name;
int age;
Person.fromMap(Map<String, dynamic> map) {
name = map['name'];
age = map['age'];
}
}
En utilisant ces méthodes, vous pouvez facilement convertir des objets de votre classe en format compatible avec SQLite pour les opérations de base de données, telles que l’insertion, la mise à jour et la récupération des données.
Exemple
class Person {
int id; // Identifiant unique pour chaque personne
String name; // Nom de la personne
int age; // Âge de la personne
// Constructeur pour initialiser les attributs d'une personne
Person({required this.id, required this.name, required this.age});
// Méthode pour convertir un objet Person en une Map
// La Map est une structure clé-valeur utile pour le stockage ou les transferts de données
Map<String, dynamic> toMap() {
return {
'id': id, // Clé "id" associée à la valeur de l'attribut id
'name': name, // Clé "name" associée au nom de la personne
'age': age, // Clé "age" associée à l'âge de la personne
};
}
// Méthode statique pour créer un objet Person à partir d'une Map
// Cette méthode est utile pour reconstruire un objet après avoir récupéré des données d'une source externe
static Person fromMap(Map<String, dynamic> map) {
return Person(
id: map['id'], // Extraction de l'identifiant à partir de la Map
name: map['name'], // Extraction du nom à partir de la Map
age: map['age'], // Extraction de l'âge à partir de la Map
);
}
}
// Exemple d'utilisation
void main() {
// Création d'un objet Person
Person person = Person(id: 1, name: "John", age: 30);
// Conversion de l'objet Person en Map grâce à toMap()
Map<String, dynamic> personMap = person.toMap();
print("Person as Map: $personMap"); // Affiche la Map résultante
// Reconstruction d'un objet Person à partir de la Map grâce à fromMap()
Person newPerson = Person.fromMap(personMap);
print("Person from Map: ${newPerson.name}, ${newPerson.age}"); // Affiche les propriétés de l'objet recréé
}
Map<String, dynamic> person = {
'name': 'John Doe',
'age': 30,
'isEmployed': true,
'height': 6.0,
'favorites': ['pizza', 'movies', 'coding'],
};
'name' est une String.
'age' est un int.
'isEmployed' est un bool.
'height' est un double.
'favorites' est une List<String>.
factory Book.fromMap(Map map) {
return Book(
title: map['title'],
author: map['author'],
pageCount: map['pageCount'],
);
}
Utiliser la classe modèle dans votre application:
- Utiliser la classe modèle dans votre application : Une fois la classe modèle créée, vous pouvez l’utiliser dans votre application Flutter pour représenter et manipuler les données stockées dans la base de données SQLite.
- Par exemple, pour un objet Movie, la classe pourrait ressembler à ceci :
- Créer un objet Movie à partir de données sous forme de Map
- Créer un objet Movie à partir de données sous forme Json
- Dans cet exemple, la classe Movie a des propriétés telles que id, title, year, et language, qui correspondent aux colonnes de la table de films.
// Définition de la classe Movie
class Movie {
// Attributs de la classe
int id; // Identifiant du film
String title; // Titre du film
int year; // Année de sortie du film
String language; // Langue du film
// Constructeur de la classe Movie
Movie({required this.id,required this.title, required this.year, required this.language});
// Méthode pour convertir l'objet en une Map pour les opérations de base de données
Map<String, dynamic> toMap() {
return {
'id': id, // Clé: 'id', Valeur: id de l'objet Movie
'title': title, // Clé: 'title', Valeur: titre de l'objet Movie
'year': year, // Clé: 'year', Valeur: année de sortie de l'objet Movie
'language': language, // Clé: 'language', Valeur: langue de l'objet Movie
};
}
// Méthode de fabrique pour créer un objet Movie à partir d'une Map
factory Movie.fromMap(Map<String, dynamic> map) {
return Movie(
id: map['id'], // Récupérer l'id depuis la Map
title: map['title'], // Récupérer le titre depuis la Map
year: map['year'], // Récupérer l'année depuis la Map
language: map['language'], // Récupérer la langue depuis la Map
);
}
}
/// Classe Movie représente un film avec des attributs immuables.
class Movie {
final int id; // Identifiant du film
final String title; // Titre du film
final String language; // Langue du film
final int year; // Année de sortie du film
/// Constructeur de la classe Movie.
/// [id], [title], [language] et [year] sont des paramètres obligatoires.
Movie({
required this.id,
required this.title,
required this.language,
required this.year,
});
/// Fabrique pour créer un objet Movie à partir d'une map JSON [data].
factory Movie.fromJson(Map<String, dynamic> data) => Movie(
id: data['id'], // Identifiant du film
title: data['title'], // Titre du film
language: data['language'], // Langue du film
year: data['year'], // Année de sortie du film
);
/// Méthode pour convertir l'objet Movie en une map de données.
Map<String, dynamic> toMap() => {
'id': id, // Identifiant du film
'title': title, // Titre du film
'language': language, // Langue du film
'year': year, // Année de sortie du film
};
}
Activité : Création d’une classe modèle Student en Flutter
- Objectif de l’activité :
- Créer une classe modèle Student pour représenter les informations d’un étudiant.
- Utiliser les concepts de base de Flutter pour définir la structure de la classe Student.
- Implémenter des méthodes pour convertir l’objet Student en une Map et vice versa.
- Instructions :
- Commencez par définir une classe Student avec les attributs suivants :
- Implémentez un constructeur pour la classe Student qui prend en paramètre les attributs nécessaires à la création d’un objet Student.
- Ajoutez une méthode toMap() à la classe Student pour convertir un objet Student en une Map contenant les données de l’étudiant.
- Ajoutez une méthode de fabrique fromMap() à la classe Student pour créer un objet Student à partir d’une Map de données.
- Testez votre implémentation en créant quelques instances de la classe Student avec des données fictives et en les convertissant en Map, puis en les recréant à partir de ces Map.
- Ressources supplémentaires :
- Vous pouvez utiliser l’exemple fourni dans la réponse précédente comme point de départ pour implémenter la classe Student.
- N’hésitez pas à consulter la documentation officielle de Flutter pour plus d’informations sur la création de classes modèles et l’utilisation de méthodes de conversion de données.
id : un identifiant unique de type int.
name : le nom de l’étudiant de type String.
age : l’âge de l’étudiant de type int.
major : la spécialisation de l’étudiant de type String.
email : l’adresse e-mail de l’étudiant de type String.
Création de la base de données et des tables
La première étape de l’utilisation de
SQLite
avec Flutter consiste à créer une base de données et une table. Pour créer une base de données, vous devez ouvrir une connexion au fichier de base de données. Vous pouvez le faire en utilisant la méthodeopenDatabase
fournie par le packagesqflite
. Une fois que vous disposez d’une connexion, vous pouvez exécuter des commandes SQL pour créer des tables et insérer des données.
-
Gèrer l’accès à la base de données de manière centralisée.
- Créons database_service.dart comme fichier, dans un dossier « services » avec une classe
DatabaseService
, où toutes les opérations de base de données seront gérées. - Un modèle Singleton sera utilisé pour créer la classe de service.
-
Un modèle Singleton dans Flutter est un modèle de conception de logiciel qui garantit qu’une classe n’a qu’une seule instance et fournit un point d’accès global à cette instance. Cela signifie qu’une fois qu’une instance de la classe est créée, toutes les autres demandes de création de cette classe renvoient simplement la référence à cette instance unique.
- Désormais, la base de données doit être initialisée avant de créer des tables ou d’effectuer des opérations de lecture/écriture.
- Ceci est indiqué dans le code ci-dessous du fichier database_service.dart.
- En Dart, une
Future
représente une valeur ou une erreur qui sera disponible à un moment donné dans le futur. Cela permet d’effectuer des opérations de manière asynchrone, ce qui est crucial pour les applications Flutter, où les interactions avec des services réseau, des fichiers, des bases de données, etc., peuvent prendre du temps et bloquer le thread principal si elles étaient synchrones. -
La fonction
initDatabase()
-
Paramètres de la fonction
openDatabase()
: path (String) :
Le chemin d’accès à la base de données sur le système de fichiers.version (int) :
La version de la base de données. Cela permet de gérer les mises à jour de schéma.onConfigure, onCreate, onUpgrade, onDowngrade (OnDatabaseConfigureFn, OnDatabaseCreateFn, OnDatabaseVersionChangeFn) :
Des callbacks qui permettent de configurer, créer, mettre à jour ou rétrograder la base de données.readOnly (bool) :
Si true, ouvre la base de données en mode lecture seule.singleInstance (bool) :
Si true, assure qu’une seule instance de la base de données est ouverte. Si false, plusieurs instances peuvent être ouvertes.exclusive (bool) :
Si true, empêche l’ouverture simultanée de la base de données par d’autres processus.vmDatabase (VmDatabase) :
Une base de données en mémoire à utiliser au lieu d’une base de données sur le disque.password (PasswordCallback) :
Un callback utilisé pour obtenir le mot de passe nécessaire pour ouvrir une base de données chiffrée.onOpen, onClose (Function) :
Des callbacks appelés lors de l’ouverture ou de la fermeture de la base de données.logStatements (bool) :
Si true, enregistre les déclarations SQL dans le log.setupSecureDatabase (bool) :
Si true, configure la base de données pour une utilisation sécurisée (par exemple, chiffrement).-
La fonction
_createTables()
-
Utilisation du patron de conception Singleton
- l’utilisation du patron de conception Singleton n’est pas obligatoire pour la classe DatabaseService. Cependant, il peut être judicieux de l’utiliser dans certains cas spécifiques.
- Voici les principales raisons pour lesquelles le patron Singleton peut être approprié dans le cas de la classe DatabaseService :
- Gestion centralisée de la base de données :
- L’accès à la base de données doit généralement être géré de manière centralisée dans une application.
- Le patron Singleton permet d’avoir une seule instance de la classe DatabaseService accessible dans toute l’application.
- Cela évite la création de multiples connexions à la base de données, ce qui pourrait entraîner des problèmes de performance et de synchronisation.
- Facilité de maintenance et d’évolution :
- Avec une seule instance de DatabaseService, il est plus facile de maintenir et de faire évoluer la logique liée à la gestion de la base de données.
- Les modifications apportées à la classe DatabaseService n’auront qu’un seul point d’impact dans l’application.
- Allocation de ressources limitées :
- L’accès à la base de données peut être une ressource limitée (connexions, transactions, etc.).
- Le patron Singleton garantit qu’il n’y a qu’une seule instance de DatabaseService, ce qui permet de mieux contrôler l’utilisation de ces ressources limitées.
- Bien que le patron Singleton ne soit pas obligatoire, il est souvent considéré comme une bonne pratique pour la gestion centralisée des ressources partagées, comme c’est le cas avec la base de données dans une application.
- Cependant, il est important de noter que le patron Singleton n’est pas adapté à tous les cas de figure. Il faut évaluer si cette approche convient réellement aux besoins de l’application et de la conception globale du système.
-
Exemple complet
- Dans le dossier lib de votre projet, créez un nouveau fichier (par exemple, database_helper.dart) pour gérer la création et la manipulation de la base de données. Importez les paquets nécessaires :
- Créez notre classe pour gérer la base de données (cas général):
- Par exemple, lors de la définition d’une catégorie Movies, la classe qui gère la base de données peut etre:
-
Activité
- Créez une nouvelle classe nommée DatabaseService.
- Ajoutez les attributs et les méthodes nécessaires pour gérer une base de données SQLite.
- Implémentez les méthodes pour initialiser la base de données, créer la table d’étudiants, insérer, mettre à jour, supprimer et interroger des données.
- Utilisez des méthodes de fabrique et des méthodes d’instance pour garantir l’accès à une seule instance de DatabaseService.
-
Exécuter votre base de données avec votre fichier main.dart
- Pour exécuter votre base de données avec votre fichier main.dart, vous devez instancier votre classe DatabaseService et utiliser ses méthodes pour interagir avec la base de données SQLite. Voici comment vous pouvez procéder :
- Importez votre classe Student et DatabaseService dans votre fichier main.dart.
- Créez une instance de DatabaseService dans votre méthode main pour initialiser la connexion à la base de données.
. . .
// Instance de la base de données.
static Database? _database;
// Getter pour accéder à l'instance de la base de données de manière asynchrone.
Future<Database> get database async {
// Si l'instance de la base de données n'est pas nulle, la retourner.
if (_database != null) return _database!;
// Sinon, initialiser la base de données et l'assigner à la variable d'instance.
_database = await initDatabase();
// Retourner l'instance de base de données initialisée.
return _database!;
}
. . .
Future<Database> initDatabase() async {
final getDirectory = await getApplicationDocumentsDirectory();
String path = getDirectory.path + 'your_database.db';
log(path);
return await openDatabase(path, onCreate: _createTables, version: 1);
}
. . .
void _createTables(Database db, int version) async {
await db.execute('''
CREATE TABLE your_table (
id INTEGER PRIMARY KEY,
name TEXT,
// ... other columns
)
''');
log('TABLE CREATED');
}
. . .
static final DatabaseService _databaseService = DatabaseService._internal();
//Cela crée une instance statique et finale de la classe DatabaseService.
//Cette instance sera la seule instance de cette classe dans toute l'application.
factory DatabaseService() => _databaseService;
//Cela définit un constructeur de fabrique qui retourne toujours l'instance statique _databaseService.
//Cela empêche la création d'instances multiples de DatabaseService.
DatabaseService._internal();
//C'est un constructeur privé qui ne peut être appelé que depuis l'intérieur de la classe.
//Cela garantit que la création de l'instance DatabaseService ne peut se faire que via le constructeur de fabrique.
static Database? _database;
//Cela définit une propriété statique et nullable qui stockera l'instance de la base de données.
Avec cette implémentation, la classe DatabaseService suit le patron de conception Singleton, ce qui garantit qu’il n’y a qu’une seule instance de cette classe dans l’application. Cela permet d’avoir un point d’accès unique à la base de données, facilitant ainsi la gestion et la maintenance du code.
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseService {
// Instance unique de DatabaseService grâce au Singleton
static final DatabaseService _databaseService = DatabaseService._internal();
// Factory permettant d'accéder à l'instance unique de DatabaseService
factory DatabaseService() => _databaseService;
// Constructeur interne privé pour empêcher l'instanciation directe
DatabaseService._internal();
// Instance de la base de données
static Database? _database;
// Méthode pour récupérer la base de données de manière asynchrone
Future<Database> get database async {
// Si la base de données existe déjà, la retourner
if (_database != null) {
return _database!;
}
// Sinon, initialiser la base de données et la retourner
_database = await _initDatabase();
return _database!;
}
// Méthode pour initialiser la base de données
Future _initDatabase() async {
// Obtenir le chemin de la base de données
final path = join(await getDatabasesPath(), 'your_database.db');
// Ouvrir la base de données
return await openDatabase(
path,
version: 1,
onCreate: _createTables,
);
}
// Méthode pour créer les tables de la base de données si nécessaire
Future<void> _createTables(Database db, int version) async {
// Exécuter la requête de création de table
await db.execute('''
CREATE TABLE your_table (
id INTEGER PRIMARY KEY,
name TEXT,
// ... autres colonnes
)
''');
}
// Ajouter ici les méthodes pour insérer, mettre à jour, supprimer et interroger les données
}
class DatabaseService {
static final DatabaseService _databaseService = DatabaseService._internal();
factory DatabaseService() => _databaseService;
DatabaseService._internal();
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await initDatabase();
return _database!;
}
Future<Database> initDatabase() async {
final getDirectory = await getApplicationDocumentsDirectory();
String path = getDirectory.path + '/movies.db';
log(path);
return await openDatabase(path, onCreate: _onCreate, version: 1);
}
void _onCreate(Database db, int version) async {
await db.execute(
'CREATE TABLE Movies(id TEXT PRIMARY KEY, title TEXT, language TEXT, year INTEGER)');
log('TABLE CREATED');
}
// Add methods to insert, update, delete, and query data
}