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
sitioNuestro 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
CrearUsando 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; }
LeerObtener 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ónActualizar 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; }
EliminarEliminar 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