Bonjour, Habr! Nous vous présentons une traduction de l'article
«Utilisation de SQLite dans Flutter» .

L'enregistrement des données est très important pour les utilisateurs, car il n'est pas pratique de charger les mêmes données à partir du réseau. Il serait plus sage de les sauvegarder localement.
Dans cet article, je vais vous montrer comment procéder à l'aide de SQLite dans Flutter-e
Pourquoi SQLite?
SQLite est le moyen le plus populaire pour stocker des données sur des appareils mobiles. Dans cet article, nous utiliserons le package sqflite pour utiliser SQLite. Sqflite est l'une des bibliothèques les plus fréquemment utilisées et pertinentes pour connecter des bases de données SQLite à Flutter.
1. Ajouter des dépendances
Dans notre projet, ouvrez le fichier
pubspec.yaml . Sous les dépendances, ajoutez la dernière version de sqflite et path_provider.
dependencies: flutter: sdk: flutter sqflite: any path_provider: any
2. Créez un client DB
Créez maintenant un nouveau fichier Database.dart. Dans ce document, créez un singleton.
Pourquoi nous avons besoin d'un singleton: nous utilisons ce modèle pour nous assurer que nous n'avons qu'une seule entité de classe et pour lui fournir un point d'entrée global.
1. Créez un constructeur privé qui ne peut être utilisé qu'à l'intérieur de cette classe.
class DBProvider { DBProvider._(); static final DBProvider db = DBProvider._(); }
2. Configurer la base de données
L'étape suivante consiste à créer l'objet de base de données et à fournir un getter où nous créerons l'objet de base de données s'il n'a pas encore été créé (initialisation paresseuse)
static Database _database; Future<Database> get database async { if (_database != null) return _database; // if _database is null we instantiate it _database = await initDB(); return _database; }
S'il n'y a aucun objet affecté à la base de données, alors nous appellerons la fonction initDB pour créer la base de données. Dans cette fonction, nous obtenons le chemin pour enregistrer la base de données et créer les tables souhaitées
initDB() async { Directory documentsDirectory = await getApplicationDocumentsDirectory(); String path = join(documentsDirectory.path, "TestDB.db"); return await openDatabase(path, version: 1, onOpen: (db) { }, onCreate: (Database db, int version) async { await db.execute("CREATE TABLE Client (" "id INTEGER PRIMARY KEY," "first_name TEXT," "last_name TEXT," "blocked BIT" ")"); }); }
3. Créez une classe modèle
Les données à l'intérieur de la base de données seront converties en Dart Maps. Nous devons créer des classes de modèle avec les méthodes toMap et fromMap.
Pour créer des classes de modèles, je vais utiliser ce
siteNotre modèle:
/// ClientModel.dart import 'dart:convert'; Client clientFromJson(String str) { final jsonData = json.decode(str); return Client.fromJson(jsonData); } String clientToJson(Client data) { final dyn = data.toJson(); return json.encode(dyn); } class Client { int id; String firstName; String lastName; bool blocked; Client({ this.id, this.firstName, this.lastName, this.blocked, }); factory Client.fromJson(Map<String, dynamic> json) => new Client( id: json["id"], firstName: json["first_name"], lastName: json["last_name"], blocked: json["blocked"], ); Map<String, dynamic> toJson() => { "id": id, "first_name": firstName, "last_name": lastName, "blocked": blocked, }; }
4. Opérations CRUD
CréerUtilisation de rawInsert:
newClient(Client newClient) async { final db = await database; var res = await db.rawInsert( "INSERT Into Client (id,first_name)" " VALUES (${newClient.id},${newClient.firstName})"); return res; }
Utilisation d'insert:
newClient(Client newClient) async { final db = await database; var res = await db.insert("Client", newClient.toMap()); return res; }
Un autre exemple utilisant un gros identifiant comme nouvel identifiant
newClient(Client newClient) async { final db = await database; //get the biggest id in the table var table = await db.rawQuery("SELECT MAX(id)+1 as id FROM Client"); int id = table.first["id"]; //insert to the table using the new id var raw = await db.rawInsert( "INSERT Into Client (id,first_name,last_name,blocked)" " VALUES (?,?,?,?)", [id, newClient.firstName, newClient.lastName, newClient.blocked]); return raw; }
LisezObtenir le client par identifiant
getClient(int id) async { final db = await database; var res =await db.query("Client", where: "id = ?", whereArgs: [id]); return res.isNotEmpty ? Client.fromMap(res.first) : Null ; }
Obtenez tous les clients avec une condition
getAllClients() async { final db = await database; var res = await db.query("Client"); List<Client> list = res.isNotEmpty ? res.map((c) => Client.fromMap(c)).toList() : []; return list; }
Obtenir uniquement les clients bloqués
getBlockedClients() async { final db = await database; var res = await db.rawQuery("SELECT * FROM Client WHERE blocked=1"); List<Client> list = res.isNotEmpty ? res.toList().map((c) => Client.fromMap(c)) : null; return list; }
Mettre à jourMettre à jour un client existant
updateClient(Client newClient) async { final db = await database; var res = await db.update("Client", newClient.toMap(), where: "id = ?", whereArgs: [newClient.id]); return res; }
Verrouillage / déverrouillage client
blockOrUnblock(Client client) async { final db = await database; Client blocked = Client( id: client.id, firstName: client.firstName, lastName: client.lastName, blocked: !client.blocked); var res = await db.update("Client", blocked.toMap(), where: "id = ?", whereArgs: [client.id]); return res; }
EffacerSupprimer un client
deleteClient(int id) async { final db = await database; db.delete("Client", where: "id = ?", whereArgs: [id]); }
Supprimer tous les clients
deleteAll() async { final db = await database; db.rawDelete("Delete * from Client"); }
Démo
Pour notre démo, nous allons créer une application simple qui affiche notre base de données.
Nous créons d'abord l'écran
Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Flutter SQLite")), body: FutureBuilder<List<Client>>( future: DBProvider.db.getAllClients(), builder: (BuildContext context, AsyncSnapshot<List<Client>> snapshot) { if (snapshot.hasData) { return ListView.builder( itemCount: snapshot.data.length, itemBuilder: (BuildContext context, int index) { Client item = snapshot.data[index]; return ListTile( title: Text(item.lastName), leading: Text(item.id.toString()), trailing: Checkbox( onChanged: (bool value) { DBProvider.db.blockClient(item); setState(() {}); }, value: item.blocked, ), ); }, ); } else { return Center(child: CircularProgressIndicator()); } }, ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () async { Client rnd = testClients[math.Random().nextInt(testClients.length)]; await DBProvider.db.newClient(rnd); setState(() {}); }, ), ); }
Remarques:
1. FutureBuilder est utilisé pour obtenir des données de la base de données
2. FAB pour initialiser les clients de test
List<Client> testClients = [ Client(firstName: "Raouf", lastName: "Rahiche", blocked: false), Client(firstName: "Zaki", lastName: "oun", blocked: true), Client(firstName: "oussama", lastName: "ali", blocked: false), ];
3. CircularProgressIndicator est affiché lorsqu'il n'y a pas de données.
4. Lorsque l'utilisateur clique sur les cases à cocher, le client est bloqué / déverrouillé
Maintenant, il est très facile d'ajouter de nouvelles fonctionnalités, par exemple, si nous voulons supprimer le client quand il est glissé, il suffit d'envelopper le ListTile dans un widget non accessible comme ceci:
return Dismissible( key: UniqueKey(), background: Container(color: Colors.red), onDismissed: (direction) { DBProvider.db.deleteClient(item.id); }, child: ListTile(...), );
Refactoring pour utiliser un modèle BLoC
Nous avons fait beaucoup de choses dans cet article, mais dans les applications réelles, l'initialisation des états dans la couche d'interface utilisateur n'est pas une bonne idée. Séparez la logique de l'interface utilisateur.
Il existe de nombreux modèles dans Flutter, mais nous utiliserons BLoC car il est le plus flexible pour la personnalisation.
Créer BLoC class ClientsBloc { ClientsBloc() { getClients(); } final _clientController = StreamController<List<Client>>.broadcast(); get clients => _clientController.stream; dispose() { _clientController.close(); } getClients() async { _clientController.sink.add(await DBProvider.db.getAllClients()); } }
Remarques:
Remarques:
1. getClients reçoit les données de la base de données (table client) de manière asynchrone. Nous utiliserons cette méthode chaque fois que nous aurons besoin de mettre à jour la table, il vaut donc la peine de la placer dans le corps du constructeur.
2. Nous avons créé StreamController.broadcast afin d'écouter les événements diffusés plus d'une fois. Dans notre exemple, cela n'a pas vraiment d'importance, car nous ne les écoutons qu'une seule fois, mais ce serait bien de les implémenter pour l'avenir.
3. N'oubliez pas de fermer les fils. De cette façon, nous empêcherons les mémoriaux. Dans notre exemple, nous les fermons à l'aide de la méthode dispose dans StatefulWidget
Regardez maintenant le code
blockUnblock(Client client) { DBProvider.db.blockOrUnblock(client); getClients(); } delete(int id) { DBProvider.db.deleteClient(id); getClients(); } add(Client client) { DBProvider.db.newClient(client); getClients(); }
Et enfin le résultat final
Les sources peuvent être trouvées ici -
Github