Usando SQLite en Flutter

Hola Habr! Le presentamos una traducción del artículo "Uso de SQLite en Flutter" .



Guardar datos es muy importante para los usuarios, ya que no es práctico cargar los mismos datos de la red. Sería más sabio salvarlos localmente.

En este artículo, demostraré cómo hacer esto usando SQLite en Flutter-e

¿Por qué SQLite?


SQLite es la forma más popular de almacenar datos en dispositivos móviles. En este artículo, utilizaremos el paquete sqflite para usar SQLite. Sqflite es una de las bibliotecas más utilizadas y relevantes para conectar bases de datos SQLite a Flutter.

1. Agregar dependencias


En nuestro proyecto, abra el archivo pubspec.yaml . Debajo de las dependencias, agregue la última versión de sqflite y path_provider.

dependencies: flutter: sdk: flutter sqflite: any path_provider: any 

2. Crear un cliente DB


Ahora cree un nuevo archivo Database.dart. En él, crea un singleton.

Por qué necesitamos un singleton: utilizamos este patrón para asegurarnos de que solo tenemos una entidad de clase y para proporcionarle un punto de entrada global.

1. Cree un constructor privado que solo pueda usarse dentro de esta clase.

 class DBProvider { DBProvider._(); static final DBProvider db = DBProvider._(); } 

2. Configurar la base de datos

El siguiente paso es crear el objeto de la base de datos y proporcionar un getter donde crearemos el objeto de la base de datos si aún no se ha creado (inicialización diferida)

 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; } 

Si no hay ningún objeto asignado a la base de datos, llamaremos a la función initDB para crear la base de datos. En esta función, obtenemos la ruta para guardar la base de datos y crear las tablas deseadas.

 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. Crear una clase de modelo


Los datos dentro de la base de datos se convertirán a Dart Maps. Necesitamos crear clases modelo con los métodos toMap y fromMap.

Para crear clases modelo, voy a usar este sitio

Nuestro modelo:

 /// 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. Operaciones CRUD


Crear

Usando 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; } 

Usando inserto:

 newClient(Client newClient) async { final db = await database; var res = await db.insert("Client", newClient.toMap()); return res; } 

Otro ejemplo usando una ID grande como una nueva ID

 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; } 

Leer

Obtener cliente por id

 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 ; } 

Obtenga todos los clientes con una condición

 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; } 

Obtenga solo clientes bloqueados

 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; } 

Actualización

Actualizar un cliente existente

 updateClient(Client newClient) async { final db = await database; var res = await db.update("Client", newClient.toMap(), where: "id = ?", whereArgs: [newClient.id]); return res; } 

Bloqueo / desbloqueo del cliente

 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; } 

Eliminar

Eliminar un cliente

 deleteClient(int id) async { final db = await database; db.delete("Client", where: "id = ?", whereArgs: [id]); } 

Eliminar todos los clientes

 deleteAll() async { final db = await database; db.rawDelete("Delete * from Client"); } 

Demo



Para nuestra demostración, crearemos una aplicación simple que muestre nuestra base de datos.

Primero hacemos la pantalla

 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(() {}); }, ), ); } 

Notas:

1. FutureBuilder se usa para obtener datos de la base de datos

2. FAB para inicializar clientes de prueba

 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 se muestra cuando no hay datos.

4. Cuando el usuario hace clic en las casillas de verificación, el cliente se bloquea / desbloquea

Ahora es muy fácil agregar nuevas funciones, por ejemplo, si queremos eliminar el cliente cuando se desliza, simplemente envuelva el ListTile en un widget Descartable como este:

 return Dismissible( key: UniqueKey(), background: Container(color: Colors.red), onDismissed: (direction) { DBProvider.db.deleteClient(item.id); }, child: ListTile(...), ); 


Refactorización para usar un patrón BLoC


Hemos hecho mucho en este artículo, pero en aplicaciones del mundo real, inicializar estados en la capa de IU no es una buena idea. Separe la lógica de la interfaz de usuario.

Hay muchos patrones en Flutter, pero usaremos BLoC ya que es el más flexible para la personalización.

Crear 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()); } } 

Notas:
Notas:

1. getClients recibe datos de la base de datos (tabla de clientes) de forma asincrónica. Utilizaremos este método siempre que necesitemos actualizar la tabla, por lo tanto, vale la pena colocarla en el cuerpo del constructor.

2. Creamos StreamController.broadcast para escuchar los eventos de transmisión más de una vez. En nuestro ejemplo, esto realmente no importa, ya que solo los escuchamos una vez, pero sería bueno implementar esto para el futuro.

3. No olvide cerrar los hilos. De esta manera evitaremos los memoriales. En nuestro ejemplo, los cerramos usando el método dispose en StatefulWidget

Ahora mira el código

 blockUnblock(Client client) { DBProvider.db.blockOrUnblock(client); getClients(); } delete(int id) { DBProvider.db.deleteClient(id); getClients(); } add(Client client) { DBProvider.db.newClient(client); getClients(); } 

Y finalmente el resultado final


Las fuentes se pueden encontrar aquí - Github

Source: https://habr.com/ru/post/es435418/


All Articles