Saltar al contenido principal

Persistir datos con SQLite

Cómo usar SQLite para almacenar y recuperar datos.

Si estás escribiendo una aplicación que necesita persistir y consultar grandes cantidades de datos en el dispositivo local, considera usar una base de datos en lugar de un archivo local o un almacén de clave-valor. En general, las bases de datos proporcionan inserciones, actualizaciones y consultas más rápidas en comparación con otras soluciones de persistencia local.

Las aplicaciones de Flutter pueden hacer uso de las bases de datos SQLite a través del plugin sqflite disponible en pub.dev. Esta receta demuestra los conceptos básicos del uso de sqflite para insertar, leer, actualizar y eliminar datos sobre varios Dogs.

Si eres nuevo en SQLite y las sentencias SQL, revisa el tutorial de SQLite para aprender los conceptos básicos antes de completar esta receta.

Esta receta utiliza los siguientes pasos:

  1. Agregar las dependencias.
  2. Define el modelo de datos Dog.
  3. Abrir la base de datos.
  4. Crea la tabla dogs.
  5. Inserta un Dog en la base de datos.
  6. Recuperar la lista de perros.
  7. Actualiza un Dog en la base de datos.
  8. Elimina un Dog de la base de datos.

1. Agregar las dependencias

#

Para trabajar con bases de datos SQLite, importa los paquetes sqflite y path.

  • El paquete sqflite proporciona clases y funciones para interactuar con una base de datos SQLite.
  • El paquete path proporciona funciones para definir la ubicación para almacenar la base de datos en el disco.

Para agregar los paquetes como dependencia, ejecuta flutter pub add:

flutter pub add sqflite path

Asegúrate de importar los paquetes en el archivo en el que vas a trabajar.

dart
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

2. Definir el modelo de datos Dog

#

Antes de crear la tabla para almacenar información sobre los perros, tómate unos momentos para definir los datos que deben almacenarse. Para este ejemplo, define una clase Dog que contenga tres datos: un id único, el name, y el age de cada perro.

dart
class Dog {
  final int id;
  final String name;
  final int age;

  const Dog({required this.id, required this.name, required this.age});
}

3. Abrir la base de datos

#

Antes de leer y escribir datos en la base de datos, abre una conexión con la base de datos. Esto implica dos pasos:

  1. Define la ruta al archivo de la base de datos utilizando getDatabasesPath() del paquete sqflite, combinado con la función join del paquete path.
  2. Abre la base de datos con la función openDatabase() de sqflite.
dart
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final database = openDatabase(
  // Set the path to the database. Note: Using the `join` function from the
  // `path` package is best practice to ensure the path is correctly
  // constructed for each platform.
  join(await getDatabasesPath(), 'doggie_database.db'),
);

4. Crear la tabla dogs

#

A continuación, crea una tabla para almacenar información sobre varios Dogs. Para este ejemplo, crea una tabla llamada dogs que defina los datos que se pueden almacenar. Cada Dog contiene un id, name, y age. Por lo tanto, estos se representan como tres columnas en la tabla dogs.

  1. El id es un int de Dart, y se almacena como un tipo de datos INTEGER de SQLite. También es una buena práctica usar un id como clave primaria de la tabla para mejorar los tiempos de consulta y actualización.
  2. El name es un String de Dart, y se almacena como un tipo de datos TEXT de SQLite.
  3. El age también es un int de Dart, y se almacena como un tipo de datos INTEGER.

Para obtener más información sobre los tipos de datos disponibles que se pueden almacenar en una base de datos SQLite, consulta la documentación oficial de tipos de datos de SQLite.

dart
final database = openDatabase(
  // Set the path to the database. Note: Using the `join` function from the
  // `path` package is best practice to ensure the path is correctly
  // constructed for each platform.
  join(await getDatabasesPath(), 'doggie_database.db'),
  // When the database is first created, create a table to store dogs.
  onCreate: (db, version) {
    // Run the CREATE TABLE statement on the database.
    return db.execute(
      'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
    );
  },
  // Set the version. This executes the onCreate function and provides a
  // path to perform database upgrades and downgrades.
  version: 1,
);

5. Insertar un Dog en la base de datos

#

Ahora que tienes una base de datos con una tabla adecuada para almacenar información sobre varios perros, es hora de leer y escribir datos.

Primero, inserta un Dog en la tabla dogs. Esto implica dos pasos:

  1. Convertir el Dog en un Map
  2. Utiliza el método insert() para almacenar el Map en la tabla dogs.
dart
class Dog {
  final int id;
  final String name;
  final int age;

  Dog({required this.id, required this.name, required this.age});

  // Convert a Dog into a Map. The keys must correspond to the names of the
  // columns in the database.
  Map<String, Object?> toMap() {
    return {'id': id, 'name': name, 'age': age};
  }

  // Implement toString to make it easier to see information about
  // each dog when using the print statement.
  @override
  String toString() {
    return 'Dog{id: $id, name: $name, age: $age}';
  }
}
dart
// Define a function that inserts dogs into the database
Future<void> insertDog(Dog dog) async {
  // Get a reference to the database.
  final db = await database;

  // Insert the Dog into the correct table. You might also specify the
  // `conflictAlgorithm` to use in case the same dog is inserted twice.
  //
  // In this case, replace any previous data.
  await db.insert(
    'dogs',
    dog.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}
dart
// Create a Dog and add it to the dogs table
var fido = Dog(id: 0, name: 'Fido', age: 35);

await insertDog(fido);

6. Recuperar la lista de Dogs

#

Ahora que hay un Dog almacenado en la base de datos, consulta la base de datos para obtener un perro específico o una lista de todos los perros. Esto implica dos pasos:

  1. Ejecuta una consulta (query) en la tabla dogs. Esto devuelve una List<Map>.
  2. Convierte la List<Map> en una List<Dog>.
dart
// A method that retrieves all the dogs from the dogs table.
Future<List<Dog>> dogs() async {
  // Get a reference to the database.
  final db = await database;

  // Query the table for all the dogs.
  final List<Map<String, Object?>> dogMaps = await db.query('dogs');

  // Convert the list of each dog's fields into a list of `Dog` objects.
  return [
    for (final {'id': id as int, 'name': name as String, 'age': age as int}
        in dogMaps)
      Dog(id: id, name: name, age: age),
  ];
}
dart
// Now, use the method above to retrieve all the dogs.
print(await dogs()); // Prints a list that include Fido.

7. Actualizar un Dog en la base de datos

#

Después de insertar información en la base de datos, es posible que desees actualizar esa información más adelante. Puedes hacerlo utilizando el método update() de la librería sqflite.

Esto implica dos pasos:

  1. Convertir el Dog en un Map.
  2. Utiliza una cláusula where para asegurarte de actualizar el Dog correcto.
dart
Future<void> updateDog(Dog dog) async {
  // Get a reference to the database.
  final db = await database;

  // Update the given Dog.
  await db.update(
    'dogs',
    dog.toMap(),
    // Ensure that the Dog has a matching id.
    where: 'id = ?',
    // Pass the Dog's id as a whereArg to prevent SQL injection.
    whereArgs: [dog.id],
  );
}
dart
// Update Fido's age and save it to the database.
fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
await updateDog(fido);

// Print the updated results.
print(await dogs()); // Prints Fido with age 42.

8. Eliminar un Dog de la base de datos

#

Además de insertar y actualizar información sobre los perros, también puedes eliminar perros de la base de datos. Para eliminar datos, utiliza el método delete() de la librería sqflite.

En esta sección, crea una función que tome un id y elimine de la base de datos el perro con el id correspondiente. Para que esto funcione, debes proporcionar una cláusula where para limitar los registros que se eliminan.

dart
Future<void> deleteDog(int id) async {
  // Get a reference to the database.
  final db = await database;

  // Remove the Dog from the database.
  await db.delete(
    'dogs',
    // Use a `where` clause to delete a specific dog.
    where: 'id = ?',
    // Pass the Dog's id as a whereArg to prevent SQL injection.
    whereArgs: [id],
  );
}

Ejemplo

#

Para ejecutar el ejemplo:

  1. Crea un nuevo proyecto de Flutter.
  2. Agrega los paquetes sqflite y path a tu pubspec.yaml.
  3. Pega el siguiente código en un nuevo archivo llamado lib/db_test.dart.
  4. Ejecuta el código con flutter run lib/db_test.dart.
dart
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

void main() async {
  // Avoid errors caused by flutter upgrade.
  // Importing 'package:flutter/widgets.dart' is required.
  WidgetsFlutterBinding.ensureInitialized();
  // Open the database and store the reference.
  final database = openDatabase(
    // Set the path to the database. Note: Using the `join` function from the
    // `path` package is best practice to ensure the path is correctly
    // constructed for each platform.
    join(await getDatabasesPath(), 'doggie_database.db'),
    // When the database is first created, create a table to store dogs.
    onCreate: (db, version) {
      // Run the CREATE TABLE statement on the database.
      return db.execute(
        'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
      );
    },
    // Set the version. This executes the onCreate function and provides a
    // path to perform database upgrades and downgrades.
    version: 1,
  );

  // Define a function that inserts dogs into the database
  Future<void> insertDog(Dog dog) async {
    // Get a reference to the database.
    final db = await database;

    // Insert the Dog into the correct table. You might also specify the
    // `conflictAlgorithm` to use in case the same dog is inserted twice.
    //
    // In this case, replace any previous data.
    await db.insert(
      'dogs',
      dog.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  // A method that retrieves all the dogs from the dogs table.
  Future<List<Dog>> dogs() async {
    // Get a reference to the database.
    final db = await database;

    // Query the table for all the dogs.
    final List<Map<String, Object?>> dogMaps = await db.query('dogs');

    // Convert the list of each dog's fields into a list of `Dog` objects.
    return [
      for (final {'id': id as int, 'name': name as String, 'age': age as int}
          in dogMaps)
        Dog(id: id, name: name, age: age),
    ];
  }

  Future<void> updateDog(Dog dog) async {
    // Get a reference to the database.
    final db = await database;

    // Update the given Dog.
    await db.update(
      'dogs',
      dog.toMap(),
      // Ensure that the Dog has a matching id.
      where: 'id = ?',
      // Pass the Dog's id as a whereArg to prevent SQL injection.
      whereArgs: [dog.id],
    );
  }

  Future<void> deleteDog(int id) async {
    // Get a reference to the database.
    final db = await database;

    // Remove the Dog from the database.
    await db.delete(
      'dogs',
      // Use a `where` clause to delete a specific dog.
      where: 'id = ?',
      // Pass the Dog's id as a whereArg to prevent SQL injection.
      whereArgs: [id],
    );
  }

  // Create a Dog and add it to the dogs table
  var fido = Dog(id: 0, name: 'Fido', age: 35);

  await insertDog(fido);

  // Now, use the method above to retrieve all the dogs.
  print(await dogs()); // Prints a list that include Fido.

  // Update Fido's age and save it to the database.
  fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
  await updateDog(fido);

  // Print the updated results.
  print(await dogs()); // Prints Fido with age 42.

  // Delete Fido from the database.
  await deleteDog(fido.id);

  // Print the list of dogs (empty).
  print(await dogs());
}

class Dog {
  final int id;
  final String name;
  final int age;

  Dog({required this.id, required this.name, required this.age});

  // Convert a Dog into a Map. The keys must correspond to the names of the
  // columns in the database.
  Map<String, Object?> toMap() {
    return {'id': id, 'name': name, 'age': age};
  }

  // Implement toString to make it easier to see information about
  // each dog when using the print statement.
  @override
  String toString() {
    return 'Dog{id: $id, name: $name, age: $age}';
  }
}

Usar relaciones entre tablas

#

En aplicaciones del mundo real, a menudo necesitas modelar relaciones entre tablas.

Por ejemplo, en lugar de almacenar todos los datos en una sola tabla, puedes separar los datos relacionados en varias tablas y conectarlos utilizando claves foráneas (foreign keys).

sql
CREATE TABLE breeds(
  id INTEGER PRIMARY KEY,
  name TEXT
);

You can execute these statements using the `db.execute()` method from the `sqflite` package.

CREATE TABLE dogs(
  id INTEGER PRIMARY KEY,
  name TEXT,
  age INTEGER,
  breed_id INTEGER,
  FOREIGN KEY (breed_id) REFERENCES breeds(id)
);

En este ejemplo, cada perro está asociado con una raza utilizando el campo breed_id. Esto te permite organizar los datos de manera más eficiente y evitar la duplicación.