Olá Habr! Apresentamos a sua atenção uma tradução do artigo
"Usando SQLite no Flutter" .

Salvar dados é muito importante para os usuários, pois é impraticável carregar os mesmos dados da rede. Seria mais sensato salvá-los localmente.
Neste artigo, demonstrarei como fazer isso usando SQLite no Flutter-e
Por que SQLite?
SQLite é a maneira mais popular de armazenar dados em dispositivos móveis. Neste artigo, usaremos o pacote sqflite para usar o SQLite. O Sqflite é uma das bibliotecas mais usadas e relevantes para conectar os bancos de dados SQLite ao Flutter.
1. Adicione dependências
Em nosso projeto, abra o arquivo
pubspec.yaml . Sob as dependências, adicione a versão mais recente do sqflite e path_provider.
dependencies: flutter: sdk: flutter sqflite: any path_provider: any
2. Crie um cliente de banco de dados
Agora crie um novo arquivo Database.dart. Nele, crie um singleton.
Por que precisamos de um singleton: usamos esse padrão para garantir que tenhamos apenas uma entidade de classe e fornecer um ponto de entrada global para ela.
1. Crie um construtor privado que só possa ser usado dentro desta classe.
class DBProvider { DBProvider._(); static final DBProvider db = DBProvider._(); }
2. Configure o banco de dados
O próximo passo é criar o objeto de banco de dados e fornecer um getter onde criaremos o objeto de banco de dados, se ele não tiver sido criado (inicialização lenta)
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; }
Se não houver um objeto atribuído ao banco de dados, chamaremos a função initDB para criar o banco de dados. Nesta função, obtemos o caminho para salvar o banco de dados e criar as tabelas desejadas
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. Crie uma classe de modelo
Os dados dentro do banco de dados serão convertidos para o Dart Maps. Precisamos criar classes de modelo com os métodos toMap e fromMap.
Para criar classes de modelo, vou usar este
siteNosso 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. Operações CRUD
CriarUsando 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 inserção:
newClient(Client newClient) async { final db = await database; var res = await db.insert("Client", newClient.toMap()); return res; }
Outro exemplo usando um ID grande como um novo 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; }
LerObter 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 ; }
Obter todos os clientes com uma condição
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; }
Obtenha apenas 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; }
UpdateAtualizar um 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; }
Bloqueio / desbloqueio do 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; }
ExcluirExcluir um cliente
deleteClient(int id) async { final db = await database; db.delete("Client", where: "id = ?", whereArgs: [id]); }
Excluir todos os clientes
deleteAll() async { final db = await database; db.rawDelete("Delete * from Client"); }
Demo
Para nossa demonstração, criaremos um aplicativo simples que exibe nosso banco de dados.
Primeiro compomos a tela
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 é usado para obter dados do banco de dados
2. FAB para inicializar clientes de teste
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 é mostrado quando não há dados.
4. Quando o usuário clica nas caixas de seleção, o cliente é bloqueado / desbloqueado
Agora é muito fácil adicionar novos recursos, por exemplo, se queremos remover o cliente quando ele é deslizado, basta agrupar o ListTile em um widget Não permitido como este:
return Dismissible( key: UniqueKey(), background: Container(color: Colors.red), onDismissed: (direction) { DBProvider.db.deleteClient(item.id); }, child: ListTile(...), );
Refatorando para usar um padrão BLoC
Fizemos muito neste artigo, mas em aplicativos do mundo real, inicializar estados na camada da interface do usuário não é uma boa ideia. Separe a lógica da interface do usuário.
Existem muitos padrões no Flutter, mas usaremos o BLoC, pois ele é o mais flexível para personalização.
Criar 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 recebe dados do banco de dados (tabela Client) de forma assíncrona. Usaremos esse método sempre que precisarmos atualizar a tabela; portanto, vale a pena colocá-lo no corpo do construtor.
2. Criamos StreamController.broadcast para ouvir eventos de transmissão mais de uma vez. No nosso exemplo, isso realmente não importa, pois apenas os ouvimos uma vez, mas seria bom implementá-lo para o futuro.
3. Não se esqueça de fechar os fios. Desta forma, impediremos memoriais. Em nosso exemplo, nós os fechamos usando o método dispose em StatefulWidget
Agora veja o 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(); }
E finalmente o resultado final
As fontes podem ser encontradas aqui -
Github