SQFlite para manejar una base de datos con SQLite en Flutter

- Andrés Cruz

In english

Una base de datos son uno de los elementos principales que comparten las aplicaciones hoy en día, en prácticamente todas las aplicaciones hoy en dia, siempre hay datos que se registran de manera persistente; cuando se quiere almacenar datos estructurados de manera persistente en una aplicación, lo primero que se nos pasa por la cabeza es usar una base de datos.

En Android, la base de datos usada por defecto, es SQLite; la cual no es más que un archivo en donde se guardan todos nuestros registros estructurados.

SQLite es un motor de base de datos SQL pequeño, rápido, autónomo, de alta confiabilidad y con todas las funciones. SQLite es el motor de base de datos más utilizado en el mundo. SQLite está integrado en todos los teléfonos móviles y en la mayoría de las computadoras y viene incluido dentro de innumerables otras aplicaciones que la gente usa todos los días.

En Flutter, tenemos varias opciones para almacenar datos de manera persistente, e inclusive, podemos usar SQLite para tal fin; aunque, a diferencia de Android nativo, en Flutter no soporta de manera nativa SQLite si no, tenemos que usar un plugin con el cual podemos aprovechar esta característica; el mismo se llama como SQFlite el cual habilita poder usar una base de datos SQLite en Flutter:

https://pub.dev/packages/sqflite

Para instalarlo, lo hacemos mediante un pub:

pubspec.yaml

dependencies:
 flutter:
   sdk: flutter
 sqflite:

Funcionamiento base

SQFlite es muy sencillo de usar, como puedes suponer, al usar una base de datos en SQLite, que no es más que un archivo, un archivo que se encuentra almacenado en una carpeta de nuestra aplicación, todas las operaciones que quieras realizar sobre la misma, que serían, crear o leer una base de datos, crear, modificar, obtener o eliminar registros, todas estas operaciones son asíncronas, por lo tanto, tendremos que usar los Future para cada una de estas operaciones.

Crear y abrir una base de datos

Para poder crear o abrir una base de datos, usamos la función de:

openDatabase()

La cual recibe un path en la cual debemos de especificar la ruta a la base de datos; por lo tanto, tenemos dos factores aquí:

  1. El path de donde se guarda la base de datos.
  2. El nombre de la base de datos.

Para obtener el path de la base de datos, también tenemos una función de ayuda la cual devuelve el path por defecto usado para registrar la base de datos:

getDatabasesPath()

Finalmente, el código para crear la base de datos es:

lib\helpers\db_helper.dart

import 'package:place/models/place.dart';
import 'package:sqflite/sqflite.dart';

import 'package:path/path.dart' as path;

class DBHelper {
 static Future<Database> _openDB() async {
   return openDatabase(path.join(await getDatabasesPath(), 'sites.db'),
       onCreate: (db, version) {
     return db.execute(
         "CREATE TABLE places (id INTEGER PRIMARY KEY, name TEXT, image TEXT)");
   }, version: 1);
 }
}

Vamos a conocer las operaciones tipo CRUD que podemos realizar sobre una base de datos SQFlite.

Insertar registros

Para insertar registros, en la base de datos, se usa la función de insert() que recibe dos parámetros:

  1. La tabla donde se quiere insertar los registros.
  2. El mapa, con los datos que se quieren insertar.

En cuanto al mapa, como puedes suponer, se va a usar la función llamada toMap() definida en el modelo de Place.

Recuerda que, para hacer cualquier operación sobre la base de datos, es necesario abrir la misma, y para eso, se usa la función _openDB() creada anteriormente.

Finalmente, el código de la función de insertar:

lib\helpers\db_helper.dart

import 'package:place/models/place.dart';
import 'package:sqflite/sqflite.dart';

import 'package:path/path.dart' as path;

class DBHelper {
 static Future<Database> _openDB() async {
  // ***
 }

 static Future<int> insert(Place place) async {
   Database database = await _openDB();
   return database.insert("places", place.toMap());
 }
}

Obtener el listado de todos los registros

Para obtener un listado de todos los registros, se usa la función llamada query() en la cual se indica el nombre de la tabla; esta función devuelve un listado de mapas, que es convertido a un listado de objetos, usando la función de List.generate() en la cual se itera cada uno de los registros devueltos en un mapa y convertidos a un objeto:

lib\helpers\db_helper.dart

import 'package:place/models/place.dart';
import 'package:sqflite/sqflite.dart';

import 'package:path/path.dart' as path;

class DBHelper {
 static Future<Database> _openDB() async {
  // ***
 }

 static Future<int> insert(Place place) async {
  // ***
 }

 static Future<List<Place>> places() async {
   Database database = await _openDB();

   final List<Map<String, dynamic>> placesMap = await database.query("places");

   for (var p in placesMap) {
     print("${p['id']}  ${p['name']} ");
   }

   return List.generate(
       placesMap.length,
       (i) => Place(
           id: placesMap[i]['id'],
           name: placesMap[i]['name'],
           image: placesMap[i]['image']));
 }
}

Actualizar registros

La función de actualizar es similar a la de insertar; pero, se define la cláusula where para filtrar los registros que se quieren actualizar; en este caso en particular, sería un solo registro buscando por el id del sitio:
where, específica las condiciones o condición por las cuales quieres filtrar, indicando la columna, operador y el valor.
whereArgs, especifica los valores o valor a pasar en el parámetro where, estos son esquemas típicos, de separar la condición de los datos, para ayudar a prevenir el "SQL attack".

import 'package:place/models/place.dart';
import 'package:sqflite/sqflite.dart';

import 'package:path/path.dart' as path;

class DBHelper {
 static Future<Database> _openDB() async {
  // ***
 }

 static Future<int> insert(Place place) async {
  // ***
 }

 static Future<List<Place>> places() async {
   // ***
 }

 static Future<int> update(Place place) async {
   Database database = await _openDB();
   return database.update("places", place.toMap(),
       where: 'id = ?', whereArgs: [place.id]);
 } 
}

Eliminar registros

Para eliminar registros, se aplica la misma lógica que para actualizar, indicando la consulta where, pero, en este caso, no se especifican los datos para crear o actualizar:

import 'package:place/models/place.dart';
import 'package:sqflite/sqflite.dart';

import 'package:path/path.dart' as path;

class DBHelper {
 static Future<Database> _openDB() async {
  // ***
 }

 static Future<int> insert(Place place) async {
  // ***
 }

 static Future<List<Place>> places() async {
   // ***
 }

 static Future<int> update(Place place) async {
   // ***
 }

 static Future<int> delete(Place place) async {
   Database database = await _openDB();
   return database.delete("places", where: 'id = ?', whereArgs: [place.id]);
 }
}

El uso de todas las funciones con las cuales se van a realizar operaciones en la base de datos, son de tipo asíncronas, con las cuales, no hay necesidad de crear instancias de esta clase.

Recuerda que este material es parte de mi curso completo sobre Flutter.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy