Introdução a Base de Dados - SQFLite

Base de dados... um estudo do sqflite

INTRODUÇÃO

Base de dados é uma forma estruturada para a organização de dados em tabelas. Ela se estrutura basicamente da seguinte forma:

Simplificando, Base de Dados é um repositório eletrônico onde podemos por dados, na tabela acima temos no id 1 o nome Diego e idade 30.

Podemos também relacionar tabelas, se criarmos outra tabela, podemos vinculá-las por meio de um id, por exemplo.

Antes de começarmos, irei explicar como iremos ler este artigo: para que o leitor não se perca, estarei sempre postando o código completo, os códigos novos estarão em negrito. O leitor pode notar também que antes do primeiro código será especificado qual arquivo estamos editando. Espero que gostem da maneira pela qual estamos passando tais informações. Lembrando que sou um aprendiz, então em caso de algum erro, favor comentar que iremos arrumar.

CRIANDO AS CLASSES DO BANCO DE DADOS

Primeiramente iremos criar um aplicativo simples no Flutter e adicionar os packages (plugins) https://pub.dartlang.org/packages/sqflite e https://pub.dartlang.org/packages/path_provider em pubspec.yaml.

Iremos criar algumas pastas no diretório lib:

lib/ui

lib/utils

lib/models

Deletar todo o conteúdo do nosso main.dart gerado pelo flutter e colocar o código abaixo:

MAIN.DART

import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';

void main() => runApp(new MeuApp());

class MeuApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.red,
),
home: Scaffold(
appBar: AppBar(
title: Text("Meu App"),
backgroundColor: Colors.black38,
),
),
);
}
}

Na pasta ui iremos criar um novo arquivo bd.dart onde terá todas as funções necessárias à nossa base de dados:

BD.DART


Primeiramente iremos importar os plugins que serão necessários:

import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

Criando nossa classe BD com algumas variáveis, lembrando que a variável static nunca muda. criaremos uma classe factory também porque não será recriada sempre que chamarmos a classe BD (POO) e criaremos um esqueleto da nossa tabale de banco de dados.

class BD {
// criaremos uma variável static, pois nunca irá mudar
static final BD _instance = new BD.internal();

// criaremos uma classe factory porque não será recriada sempre que chamarmos a classe BD (POO)
factory BD() => _instance;

// internal é um construtor então toda vez que precisamos é só instanciá-lo
BD.internal();

// criando o esqueleto da nossa tabela do banco de dados
final String tabelaUsuario = "tabelaUsuario";
final String colunaId = "id";
final String colunaNome = "nome";
final String colunaSenha = "senha";

//Database é a classe do SQFlite que iremos usar, por isso iremos criá-la
static Database _db;
}

Dentro dessa classe BD iremos também iniciar o Banco de dados de forma asssincrona, acessar os diretórios nativos do Android e criarmos o arquivo bd_principal.db:

class BD {
// Código anterior

// sempre que formos acessar alguma coisa utilizar o future, pois ele é uma transação alheia
Future<Database> get db async {
// se o _db existe na memória
if(_db != null){
//caso exista, retorna este _bd existente
return _db;
}
// chamamos agora o initBd que irá iniciar o nosso banco de dados
_db = await initBd();
return _db;
}

// iniciando nosso banco de dados em async pois ele é uma transição
initBd() async {
// Directory faz parte do plugin dart:io e o getApplicationDocumentsDirectory() faz parte do path_provider
// aqui nós estamos acessando o diretório nativo do android
Directory documentoDiretorio = await getApplicationDocumentsDirectory();

// o join() junta duas coisas, no caso iremos juntar o diretorio juntamente com o nosso banco de dados
String caminho = join(
documentoDiretorio.path, "bd_principal.db"
);

// após ter acesso ao local do nosso BD, iremos abri-lo
var nossoBD = await openDatabase(caminho, version: 1, onCreate: _onCreate);
return nossoBD;

}
}

Agora iremos criar nosso método chamado de _onCreate que terá a função de criar as tabelas de nosso banco de dados:

class BD {
// código anterior

//criando o método _onCreate que irá criar o nosso BD
void _onCreate(Database db, int version) async {
// aqui iremos colocar o SQL que é outra linguagem, por isso, colocaremos
// dentro de aspas, pois é string
await db.execute("CREATE TABLE $tabelaUsuario($colunaId INTEGER PRIMARY KEY,"
"$colunaNome TEXT,"
"$colunaSenha TEXT)");
}
}

Antes de darmos continuidade, iremos criar o modelo de objeto chamaod de Usuario para que possamos criar um CRUD, no caso de inserção de dados. Para isso, iremos criar o nosso novo arquivo na pasta models chamado usuario.dart

USUARIO.DART


class Usuario {
String _nome;
String _senha;
int _id;

// criando um construtor
Usuario(this._nome, this._senha);

//mapeando o usuario para podermos acessar os valores do usuário
// utilizaremos dynamic, pois não sabemos o que é cada um
// criaremos outro construtor
Usuario.map(dynamic obj) {
// quando passamos um nome ele já recebe como objeto
this._nome = obj['nome'];
this._senha = obj['senha'];
this._id = obj['id'];
}
}

Criaremos alguns gets que possibilitará nós acessarmos o nome, senha e id do nosso usuário:

class Usuario {
// Código anterior

String get nome => _nome;
String get senha => _senha;
int get id => _id;
}

Criando uma função chamada toMap que irá acessar os nossos dados (key-value) 

class Usuario {
// código anterior

// o map irá acessar uma key, como string, e depois uma chave que eu deseje
Map<String, dynamic> toMap() {
//instanciando o mapa
var mapa = new Map<String, dynamic>();
// agora iremos fazer o inverso do anterio, iremos colocar o conteúdo
// dos atributos no mapa, ficará como se fosse um json
mapa["nome"] = _nome;
mapa["senha"] = _senha;

// verificar se o id tem alguma coisa, para não adicionar nada em branco
if (id != null){
mapa["id"] = _id;
}
return mapa;
}
}

Agora iremos fazer o reverso do construtor toMap que acabamos de criar, criando outro construtor chamado de Usuario:

class Usuario {
// código anterior

Usuario.fromMap(Map<String, dynamic> mapa) {
// pegarei o mapa que estamos recebendo da funcao acima e colocando em _nome
this._nome = mapa['nome'];
this._id = mapa['id'];
this._senha = mapa['senha'];
}
}

Pronto, agora que já criamos o objeto Usuario, já podemos utilizá-lo no nosso bd.dart, para isso criaremos a função inserirUsuario e importaremos o modelo de usuário que acabamos de criar import "../models/usuario.dart"; :

BD.DART


class BD {

// código anterior...

// criando o método de inserção de dados
// Iremos colocar o objeto que acabamos de criar chamado Usuario
// quando inserimos algo em nosso bd ele retorna um número que é o index
// por isso que o future retorna um inteiro

Future<int> inserirUsuario(Usuario usuario) async {
// o db aqui é do get escrito lá no início
// ao inserirmos um usuario, temos que criar um já esta instância de db
// esse db irá retornar um database
var bdCliente = await db;

// para inserir temos que passar uma tabela e valores que pode ser um mapa
// ese mapa é aquele que criamos em usuarios.dart
int res = await bdCliente.insert("$tabelaUsuario", usuario.toMap());

// aqui irá retornar um número
return res;
}

}

Para podermos angariarmos estes usuário iremos que criamos iremos criar uma nova função chamada pegarUsuarios():

class BD {
// código anterior...

// para podermos pegar os usuarios criados
Future<List> pegarUsuarios() async {
// praticamente iremos fazer a mesma coisa da função de inserir
var bdCliente = await db;

// rawQuery é como se fosse um pedido, então iremos solicitar todos os dados
var res = await bdCliente.rawQuery("SELECT * FROM $tabelaUsuario");
// agora iremos retornar uma list
return res.toList();
}

}

Criando a contagem dos dados para podermos saber quantos itens tem dentro da nossa tabela utilizaremos o Sqflite.firstIntValue. Também criaremos a função que pegará somente um usuário.

class BD {
// código anterior...

Future<int> pegarContagem() async {
var bdCliente = await db;
// para pegarmos a contagem temos que ir na SQFlite e utilizar o firstIntValue
return Sqflite.firstIntValue(await bdCliente.rawQuery("SELECT COUNT(*) FROM $tabelaUsuario"));
}

// iremos mostrar somente um usuário
Future<Usuario> pegarUsuario(int id) async {
var bdCliente = await db;
var res = await bdCliente.rawQuery("SELECT * FROM $tabelaUsuario"
" WHERE $colunaId = $id");

// verificando se a lista retorna nada
if (res.length == 0) return null;

// iremos retornar um mapa dos dados, pega só o primeiro
return new Usuario.fromMap(res.first);
}

}

CUIDADO! É bastante importante manter o espaço antes de WHERE na tag SQL. 

Criando a função de deletar:

class BD {
// código anterior...

Future<int> apagarUsuario(int id) async {
var bdCliente = await db;

// where é o local de onde iremos deletar os dados
return await bdCliente.delete(tabelaUsuario,
where: "$colunaId = ?", whereArgs: [id]);
}

}

Criando a função de editar o usuário:

class BD {
// código anterior...

Future<int> editarUsuario(Usuario usuario) async {
var bdCliente = await db;

// usaremos o toMap
return await bdCliente.update(tabelaUsuario,
usuario.toMap(), where: "$colunaId = ?", whereArgs: [usuario.id]
);
}

}

Fechando a nossa base de dados, pois quando criamos a nossa base dedos ela ficou aberta, mas é sempre bom fechar para não ocupar muita memória.

class BD {
// código anterior...

Future fechar() async {
var bdCliente = await db;

return bdCliente.close();
}

}

A partir daqui já temos toda a base para poder utilizar nosso aplicativo, show né? Então, vamos colocar o nosso main() assincrono e não se esqueça de importar o bd.dart e do nosso modelo de usuário:

MAIN.DART


import 'ui/BD.dart';
import 'models/usuario.dart';


void main() async {
// criando nossa classe BD
var db = new BD();

runApp(
new MeuApp()
);
}

class MeuApp extends StatelessWidget {
// ... código do widget
}
}

Como fazer para adicionar um usuário? utilizaremos a função criada em BD.dart chamada inserirUsuario():

// código de importacoes

void main() async {
var db = new BD();

//add usuario que irá retornar um int
int usuarioSalvo = await db.inserirUsuario(new Usuario("Pedro", "senha"));
print("usuário inserido $usuarioSalvo");

runApp(
new MeuApp()
);
}

Vamos usar as funções criadas?

  • - Contagem do total de dados cadastrados: int contagem =await db.pegarContagem();
  • - Para pegar um usuário salvo precisamos declarar primeiro Usuario ana = await db.pegarUsuario(1);
  • - Mostrar o usuário pego em um mapa: debugPrint("Usuario: ${ana.toMap()}");
  • - Mostrar o usuário pego pelo nome somente: debugPrint("Usuario: ${ana.nome}");
  • - Para apagar um usuário: int usuario = await db.apagarUsuario(1);

OBS. Todos os ids criados são únicos, então após apagar o id 1, não existirá outro no lugar dele.

Para atualizar o usuário criado, tem que ser na mesma ordem do mapa criado:

Usuario atualizarAna = Usuario.fromMap(
{
"nome" : "Ana Joada",
"senha" : "MudeiSenha",
"id" : 1
}
);
int atualizado = await db.editarUsuario(atualizarAna);
debugPrint("Atualizado: $atualizado");

Para retornar todos os usuário, devemos criar uma lista, antes de main() você cria List _todosUsuarios; e depois vamos utilizar a função criada pegarUsuario() para adicionar a lista. Posteriormente iremos utilizar o for para listar estes usuário:

_todosUsuarios = await db.pegarUsuarios();
for (int i = 0; i < _todosUsuarios.length; i++){
Usuario usuario = Usuario.map(_todosUsuarios[i]);
debugPrint("Usuario: ${usuario.nome}, Id: ${usuario.id}");
}

Vamos para a melhor parte? Criar um Listview e colocar todas as informações ali dentro. 

Em nossa main.dart iremos adicionar um ListView.builder.

OBS. Lembrando que tem que usar o _todosUsuarios = await db.pegarUsuarios(); para pegar todos os usuários listados na tabela e não aparecer  erro de null.

//código anterior

class MeuApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.red,
),
home: Scaffold(
appBar: AppBar(
title: Text("Meu App"),
backgroundColor: Colors.black38,
),
// o "_" no itembuilder puxa o mesmo BuildContext context da árvore
body: ListView.builder(
itemCount: _todosUsuarios.length,
itemBuilder: (_, int posicao){
return Card(
color: Colors.white,
elevation: 2.0,
child: ListTile(
leading: CircleAvatar(
child: Text("${Usuario.fromMap(_todosUsuarios[posicao]).nome.substring(0,1)}"),
),
title: Text("Usuario: ${Usuario.fromMap(_todosUsuarios[posicao]).nome}"),
),
);
}
),
),
);
}
}

Essa é a tela do nosso app criada:

 

Muito bom, o resultado final ficou simples, mas bem efetivo. Creio que com o artigo aqui criado você já tenha bastante material para poder desenvolver seu app de modo offline. Boa sorte e espero que tenha gostado. Não deixe de comentar...

130 Visualizações