Saltar al contenido
Codingpizza
Volver

Guardar en base de datos con Flutter

Editar página

Guardar en base de datos con Flutter

Photo by Tim Evans on Unsplash.

Almacenar texto en la base de datos es junto a las llamadas a red, una de las operaciones que como desarrolladores más tenemos que realizar. Algunas veces, necesitamos guardar la información de los servidores en nuestra app para que los usuarios puedan abrir la app y ver información previamente guardada antes de que se actualice con nuevo contenido, o podemos también tener que implementar un modo offline.

En este artículo, vamos a hablar sobre cómo almacenar información en una app con Flutter. Nuestra app será una aplicación que almacenará unas Tareas por realizar (Todo’s), las cuales podremos consultar luego.

Una lista de las cosas que vamos a necesitar:

/images/Screenshot_1578338767.png

Primero, vamos a crear nuestra clase Todo.

class Todo {
  final int id;
  final String content;
  final String title;
  static const String TABLENAME = "todos";

  Todo({this.id, this.content, this.title});

  Map<String, dynamic> toMap() {
    return {'id': id, 'content': content, 'title': title};
  }
}

Esta es la clase Todo la cual contiene un id, el contenido del Todo y el título. Además, agregamos una constante que incluye el nombre de la tabla de Todos, así como también una función toMap que utilizaremos más adelante.

Con la clase Todo creada, vamos a agregar nuestra dependencia de la siguiente forma en el archivo pubspec.yml.

dependencies:
  flutter:
    sdk: flutter
  sqflite:
  path:

Ahora tenemos que crear nuestra clase DatabaseHelper, la cual tiene la función de tratar con la base de datos.

class DatabaseHelper {
  //Create a private constructor
  DatabaseHelper._();

  static const databaseName = 'todos_database.db';
  static final DatabaseHelper instance = DatabaseHelper._();
  static Database _database;

  Future<Database> get database async {
    if (_database == null) {
      return await initializeDatabase();
    }
    return _database;
  }

  initializeDatabase() async {
    return await openDatabase(join(await getDatabasesPath(), databaseName),
        version: 1, onCreate: (Database db, int version) async {
      await db.execute(
          "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title TEXT, content TEXT)");
    });
  }
}

Tenemos varias cosas de las que hablar sobre esta clase. Para empezar, acceder a una base de datos es una operación asíncrona, lo que significa que debemos utilizar Futures para inicializar la propiedad database.

En la función initializeDatabase vamos a inicializar una base de datos siempre y cuando no tengamos una ya creada previamente, para inicializarla vamos a llamar la función initializeDatabase y dentro de ella llamaremos a la función openDatabase de la librería de sqflite.

La función openDatabase necesita los siguientes parámetros:

La función insertTodo() toma como parámetro un objeto Todo. Obtiene la base de datos que habíamos creado previamente y utiliza la función de la librería sqflite execute() para insertar los datos en nuestra base de datos. A esta función le vamos a pasar los siguientes parámetros.

Finalmente, podemos crear nuestra Screen la cual nos servirá para crear nuestro Todo object. Dado que los campos serán editables necesitaremos utilizar un StatefulWidget con su respectivo State. Los widgets que vamos a utilizar son Textfields y FloatingActionButton para guardar el Todo.

class CreateTodoScreen extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _CreateTodoState();
  }
}

class CreateTodoScreen extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _CreateTodoState();
  }
}

class _CreateTodoState extends State<CreateTodoScreen> {
  final descriptionTextController = TextEditingController();
  final titleTextController = TextEditingController();

  @override
  void dispose() {
    super.dispose();
    descriptionTextController.dispose();
    titleTextController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Retrieve Text Input'),
      ),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Title"),
              maxLines: 1,
              controller: titleTextController,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Description"),
              maxLines: 10,
              controller: descriptionTextController,
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.check),
          onPressed: () {
            DatabaseHelper.instance.insertTodo(Todo(
              title: titleTextController.text,
              content: descriptionTextController.text));
            Navigator.pop(context, "Your todo has been saved.");
          }),
    );
  }
}

Luego de crear nuestro State necesitamos utilizar un TextEditingController para cada uno de nuestros TextFields, estos los declaramos al principio de nuestro Widget y hacemos un override de la función dispose() para llamar a la respectiva función dispose de cada TextEditingController.

Al momento de crear nuestros TextFields, le pasamos a cada TextField su respectivo controller. Finalmente, para llamar al DatabaseHelper e indicarle que llame a la función insertTodo debemos pasarle a la propiedad onPressed del floatingActionButton una función anónima con un Todo creado usando el texto obtenido de los TextEditingController.

/images/Screenshot_1578338787.png

Tabla de contenidos

Tabla de contenidos

Actualizando nuestros Todo’s

Para actualizar nuestros Todo’s necesitamos hacer ciertas modificaciones. Primero, vamos a agregar esta nueva función a nuestro DatabaseHelper.

updateTodo(Todo todo) async {
    final db = await database;

    await db.update(Todo.TABLENAME, todo.toMap(),
        where: 'id = ?',
        whereArgs: [todo.id],
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

En esta función obtenemos el objeto database y luego llamamos a la función update() de la librería y le pasamos los siguientes parámetros.

Ahora debemos actualizar nuestra UI. Primero, vamos a modificar nuestra DetailTodoScreen para poder editar nuestro Todo. Necesitaremos agregar nuestro Todo como un parámetro opcional en el constructor del DetailTodoScreen, luego crear una property Todo en nuestro Widget y debido a que es un StatefulWidget debemos pasarle este Todo al State.

class DetailTodoScreen extends StatefulWidget {
    final Todo todo;

  const DetailTodoScreen({Key key, this.todo}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _CreateTodoState(todo);
}

En nuestro State necesitamos agregar también nuestro Todo al constructor, crear la property Todo y sobrescribir la función initState de nuestro Widget para que podamos asignar el Título y la descripción de nuestro Todo a los respectivos TextField.

class _CreateTodoState extends State<DetailTodoScreen> {
  Todo todo;
  final descriptionTextController = TextEditingController();
  final titleTextController = TextEditingController();

  _CreateTodoState(this.todo);

  @override
  void initState() {
    super.initState();
    if (todo != null) {
      descriptionTextController.text = todo.content;
      titleTextController.text = todo.title;
    }
  }

Finalmente, en la función anónima que le pasamos como parámetro onPressed vamos a llamar a otra función llamada saveTodo, la cual se encargará de insertar un nuevo Todo en caso de que no tengamos ningún Todo para editar o de modificar un Todo existente que hayamos pasado como parámetro a nuestro State. Al final debemos llamar también a la función setState.

Así es como se verá nuestro State luego de esas modificaciones.

class _CreateTodoState extends State<DetailTodoScreen> {
  Todo todo;
  final descriptionTextController = TextEditingController();
  final titleTextController = TextEditingController();

  _CreateTodoState(this.todo);

  @override
  void initState() {
    super.initState();
    if (todo != null) {
      descriptionTextController.text = todo.content;
      titleTextController.text = todo.title;
    }
  }

  @override
  void dispose() {
    super.dispose();
    descriptionTextController.dispose();
    titleTextController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Retrieve Text Input'),
      ),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Title"),
              maxLines: 1,
              controller: titleTextController,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Description"),
              maxLines: 10,
              controller: descriptionTextController,
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.check),
          onPressed: () async {
            _saveTodo(titleTextController.text, descriptionTextController.text);
            setState(() {});
          }),
    );
  }

  _saveTodo(String title, String content) async {
    if (todo == null) {
      DatabaseHelper.instance.insertTodo(Todo(
          title: titleTextController.text,
          content: descriptionTextController.text));
      Navigator.pop(context, "Your todo has been saved.");
    } else {
      await DatabaseHelper.instance
          .updateTodo(Todo(id: todo.id, title: title, content: content));
      Navigator.pop(context);
    }
  }
}

Deleting a Todo

Para borrar un Todo necesitamos comenzar por borrar una función de nuestra clase DatabaseHelper.

deleteTodo(int id) async {
    var db = await database;
    db.delete(Todo.TABLENAME, where: 'id = ?', whereArgs: [id]);
  }

Esta función llama a la función delete de la librería Sqflite a la cual le pasamos el nombre de la tabla la cláusula where y el id del Todo a eliminar.

Luego, para agregar una función de borrado en nuestra lista de todos vamos a necesitar realizar los siguientes pasos. Primero, en nuestro ListTile widget vamos a necesitar a utilizar un parámetro opcional llamado trailing, en este parámetro vamos a pasarle un IconButton, dentro de ese parámetro onPressed vamos a pasarle una función anónima que llamará a la función del DatabaseHelper encargada de borrar el Todo.

class  ReadTodoScreen extends StatefulWidget {
  @override
  _ReadTodoScreenState createState() => _ReadTodoScreenState();
}

class _ReadTodoScreenState extends State<ReadTodoScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Saved Todos'),
      ),
      body: FutureBuilder<List<Todo>>(
        future: DatabaseHelper.instance.retrieveTodos(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data.length,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text(snapshot.data[index].title),
                  leading: Text(snapshot.data[index].id.toString()),
                  subtitle: Text(snapshot.data[index].content),
                  onTap: () => _navigateToDetail(context, snapshot.data[index]),
                  trailing: IconButton(
                      alignment: Alignment.center,
                      icon: Icon(Icons.delete),
                      onPressed: () async {
                        _deleteTodo(snapshot.data[index]);
                        setState(() {});
                      }),
                );
              },
            );
          } else if (snapshot.hasError) {
            return Text("Oops!");
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

_deleteTodo(Todo todo) {
  DatabaseHelper.instance.deleteTodo(todo.id);
}

_navigateToDetail(BuildContext context, Todo todo) async {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => DetailTodoScreen(todo: todo)),
  );
}

/images/Screenshot_1578338793.png

¡Y eso es todo! Ahora tenemos un app que puede almacenar un Todo, leerlo desde base de datos, actualizarlo y borrarlo.

El código está disponible aquí. Si quieres leer más artículos como estos o tienes alguna duda, puedes encontrarme en las siguientes redes sociales.

Instagram: www.instagram.com/codingpizza
Twitter: www.twitter.com/coding__pizza
Facebook: www.facebook.com/codingpizza
Site: www.codingpizza.com


Editar página
Comparte este artículo:

Artículo anterior
How Classes works in Dart
Artículo siguiente
Storing in database with Flutter