Como usar SQLite em Flutter

Mariana Castanheira
Flutter Portugal
Published in
6 min readSep 16, 2019

Na Flutter Portugal acreditamos que o conhecimento tem de ser acessível a todos, independentemente do grau de conhecimento de línguas estrangeiras. Assim, vamos lançar uma pequena série de artigos que vão abrangir vários tópicos: Dados persistidos, State Management, Navegação, etc…

Este artigo foi originalmente escrito por Raouf Rahiche com o título “Using SQLite in Flutter” e traduzido com autorização do autor.

Dados persistidos (Persitent Data) são muito importantes para os utilizadores, já que lhes seria inconveniente estar sempre a escrever a sua informação ou esperar que a rede carregue os mesmos dados novamente. Nestas situações, o melhor seria guardar os seus dados localmente.

Neste artigo, vou demonstrá-lo, usando SQLiteem FLutter.

Porquê SQLite?

SQLite é um dos métodos mais populares para armazenar dados localmente. Para este artigo, vamos usar o pacote (package) sqflitepara acedermos ao SQLite. Sqflite é um dos pacotes mais utilizados e atualizados para ligar às bases de dados SQLite no Flutter.

1. Adicionar a dependência ao projeto

No nosso projeto, vamos abrir o ficheiropubspec.yamle procuramos por dependencies. Por baixo de dependencies, adicionamos a última versão do sqflitee path_provider(que podem ser retiradas do pub.dev).

NOTA: Nós usamos o pacote path_providerpara obter o directório comum, como o TemporaryDirectorye ApplicationDocumentsDirectory.

2. Criação de um DB Client

Agora, no nosso projeto, vamos criar um novo ficheiro Database.dart. Neste ficheiro, vamos precisar de criar um singleton.

Porque é que precisamos de um singleton: usamos o padrão singleton para assegurar que temos apenas uma class instance (instância de classe) e que providenciamos um ponto de acesso global a ele.

1- Criação de um construtor privado que possa ser usado apenas dentro da classe:

2- Preparação da base de dados

A seguir vamos criar o objeto da base de dados e fornecer-lhe um getter, que irá criar uma instância da base de dados, se já não tiver sido criada (lazy initialization, inicialização diferida).

Se não houver nenhum objeto atribuído à base de dados, usamos a função initDB para criar a base de dados. Nesta função, iremos obter o directório onde iremos armazenar a base de dados e criar as tabelas que desejamos:

NOTA: O nome da base de dados é TestDBe a única tabela que temos chama-se Client. Se não sabe o que é que está a acontecer, precisa mesmo de ir aprender sobre SQL (Structured Query Language, ou em português, Linguagem de Consulta Estruturada); é mais importante do que água.

3. Criação da Model Class (Classe Modelo)

Os dados dentro da nossa base de dados vão ser convertidos em Dart Maps, por isso, primeiro precisamos de criar as Model Classes com os métodos toMape fromMap. Não vou abordar como é que isto se faz manualmente, para isso poderemos ler este artigo de Poojã Bhaumik.

Para criar as nossas model classes, vamos usar este website. Se ainda não o tem guardado como favorito, deveria mesmo fazê-lo :)

Pode clicar aqui para ver como tudo funciona.

O nosso modelo:

4. Operações CRUD

Criar (Create)

O pacote SQFLitefaculta duas maneiras de lidar com estas operações, usando consultas (queries) RawSQLou o nome da tabela e um mapa que contenha os dados:

Usando rawInsert :

Usando insert:

Outro exemplo usando o maior ID (identificador) como um novo ID:

Ler (Read)

Obter Client através de um ID

No código acima, nós fornecemos a consulta com um idcomo argumento usando o whereArgs. Depois fazemos return ao primeiro resultado se a lista não estiver vazia, caso contrário retornamos null.

Obter todos os Clients com uma condição

Neste exemplo, usamos o rawQuerye mapeamos a lista de resultados para uma lista de objetos do Client:

Exemplo: Apenas obter os Blocked Clients

Atualizar (Update)

Atualizar um Client já existente

Exemplo: Bloquear (block) ou desbloquear (unblock) um Client.

Apagar (Delete)

Apagar um Client

Apagar todos os Clients

Demonstração

Para a nossa demonstração vamos criar uma aplicação Flutter simples para interagir com a nossa base de dados.

Vamos começar com a interface (layout) da aplicação:

Notas:

1- O FlutterBuilderé usado para obter dados da base de dados.

2- O FAB adiciona um cliente aleatório à base de dados quando é clicado.

3- É mostrado um CircularProgressIndicatorse não existirem dados.

4- Quando o utilizador clicar na checkbox, o cliente será bloqueado ou desbloqueado, de acordo com o estado actual.

Agora é bastante fácil adicionar novas funcionalidades, como por exemplo, se quisermos eliminar um cliente quando o item é arrastado (swiped), simplesmente envolvemos (wrap) um widget ListTilecom um widget Dismissibledesta forma:

Para a nossa função OnDismissedvamos usar o fornecedor (provider) da base de dados para chamar o método deleteClient. Como argumento, vamos usar o ID do item.

Refatoração (Refactoring) para usar o padrão BLoC

Fizémos imenso neste artigo, mas numa aplicação no mundo real, manter o estado (state) junto com a UI (User Interface) não é propriamente uma coisa boa. Pelo contrário, devemos sempre mantê-los afastados.

Existem muitos padrões para gerir o estado no Flutter, mas vamos utilizar o BLoC neste artigo por ser muito flexivel.

Criação do BLoC:

Notas:

1- getClientsirá obter os dados a partir da base de dados (tabela Client) assincronamente.Vamos chamar este método sempre que atualizarmos a tabela, daí o motivo para o colocar dentro do corpo do construtor.

2- Utilizamos StreamController<T>.broadcastno construtor para que nos seja possível ouvir (listen) o stream mais do que uma vez. No nosso exemplo não faz grande diferença, visto que só estamos a escutar o stream uma vez, mas é bom considerar casos em que queiramos ouvir o stream mais do que uma vez.

3- Não nos podemos esquecera de fechar o stream. Isto previne que tenhamos derrames de memória (memory leaks). No nosso exemplo, vamos fechá-lo usando o método dispose do nosso StatefulWidget.

Agora, vamos adicionar alguns métodos ao nosso BLoC para interagir com a base de dados:

E isto é tudo quanto ao nosso BLoC!

O nosso próximo passo seria encontrar uma forma de disponibilizar o nosso bloc aos nossos widgets. Precisamos de uma maneira de tornar o bloc acessível a partir de diferentes partes da árvore (tree), de modo a que o consigamos libertar da memória quando não está a ser usado.

Para tal, podemos dar uma vista de olhos a esta biblioteca, por Remi Rousselet.

No nosso caso, o bloc só irá ser usado por um widget de forma a que o possamos declarar e dispor dele do nosso stateful widget.

A seguir, precisamos de usar um StreamBuilder, em vez do FutureBuilder. Isto porque agora estamos a escutar o stream (stream dos clients) em vez de um future.

O último passo seria refatorar (refactor) o nosso código para que chamássemos os nossos métodos a partir do nosso bloc e não diretamente da base de dados:

Aqui está o resultado final:

Finalmente, pode encontrar a origem do código para este exemplo neste repo (veja o setor sqlite_demo_bloc para ver a nova versão após refatorar). Espero que tenham gostado deste artigo.

--

--

Mariana Castanheira
Flutter Portugal

Translator (English-Portuguese/Portuguese-English). Helping Flutter Portugal for the time being.