# CRUD (Create, Read, Update, Delete) com Firebase Cloud Firestore no Flutter

Fala, devs. Blz? Hoje vamos dar continuidade a [**série**](https://ianoliveira.hashnode.dev/serie-dominando-o-firebase-em-aplicativos-flutter) "Dominando o Firebase em Aplicativos Flutter" uma sequência de artigos sobre as principais funcionalidades do Firebase e como integrá-las ao seu aplicativo Flutter.💙

Seja para autenticação de usuários, armazenamento de dados, envio de notificações push ou captura e análise de erros, seus produtos sempre me ofereceram a solução perfeita.

Segundo o [**blog oficial do Firebase**](https://firebase.blog/posts/2022/05/whats-new-at-google-io) "Mais de 3 milhões de desenvolvedores, de startups a empresas, usam o Firebase para alcançar e envolver bilhões de usuários"

Hoje meu objetivo é abordar o uso do Cloud Firebase um banco de dados NoSQL flexível e escalonável, criado com a infraestrutura do Google Cloud, para armazenar e sincronizar dados no desenvolvimento do cliente e do servidor.

Construiremos um aplicativo de lista de tarefas de exemplo usando o Flutter e Firebase onde executaremos algumas operações com dados.

Vamos usar o design criado por [**Diaa Mohamed**](https://dribbble.com/Diaa_Mohamed_2) disponível no [Dribbble](https://dribbble.com/shots/22781589-Todo-List-App) para nos inspirar na construção do nosso app usando firebase.

Então vem comigo. 🤏🏻 👨🏻‍💻

### O que é um Crud?

CRUD é um acrônimo que representa as quatro operações básicas usadas em sistemas de gerenciamento de banco de dados relacionais ou em serviços de API que interagem com recursos. As operações são:

1. **Create (Criar)**: Envolve a criação de novos registros ou recursos no banco de dados ou sistema.
    
2. **Read (Ler)**: Envolve a recuperação de dados existentes do banco de dados ou sistema.
    
3. **Update (Atualizar)**: Envolve a modificação dos dados existentes no banco de dados ou sistema.
    
4. **Delete (Excluir)**: Envolve a remoção de registros ou recursos existentes do banco de dados ou sistema.
    

Essas operações são essenciais em muitas aplicações de software para realizar operações básicas de manipulação de dados. Quando se fala em desenvolvimento web, por exemplo, CRUD é uma abordagem comum para a implementação de funcionalidades de back-end que permitem aos usuários criar, ler, atualizar e excluir dados em um sistema.

No contexto do Firebase, um CRUD refere-se às operações básicas que podem ser realizadas em seu banco de dados em tempo real, que geralmente é o Firebase Realtime Database ou o Firestore 🔥.

### O que é Cloud Firestore?

[O Cloud Firestore](https://www.youtube.com/watch?v=v_hR4K4auoQ&list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ) é um banco de dados flexível e escalonável para desenvolvimento focado em dispositivos móveis, Web e servidores pelo Firebase e do Google Cloud.

Ele mantém seus dados sincronizados entre aplicativos clientes por meio de ouvintes em tempo real e oferece suporte off-line para que você possa criar aplicativos responsivos que funcionam independentemente da latência da rede ou da conectividade com a Internet.

Alguns dos principais recursos são:

**Sincronização em tempo real**: O Firestore oferece atualizações em tempo real para os clientes conectados. Isso significa que os dados são sincronizados automaticamente entre os diferentes dispositivos e plataformas em tempo real, permitindo uma experiência de usuário fluida e em tempo real.

**Consultas ricas**: O Firestore suporta uma variedade de consultas, incluindo consultas simples, consultas compostas e ordenação. Isso permite que os desenvolvedores recuperem dados de maneira eficiente e flexível.

**Estrutura de dados escalável**: O Firestore organiza os dados em coleções e documentos. Ele permite uma estrutura hierárquica flexível, o que facilita a organização e a consulta de dados.

**Escalabilidade automática**: O Firestore é altamente escalável e gerenciado pelo Google Cloud Platform. Ele escala automaticamente para atender às demandas de tráfego e armazenamento, garantindo alta disponibilidade e desempenho.

**Suporte off-line:** Armazena em cache os dados ativamente usados pelo aplicativo. Dessa maneira, o aplicativo poderá escrever, ler, detectar e consultar dados, mesmo que o dispositivo esteja desconectado. Quando o dispositivo retorna ao estado on-line, as alterações locais são sincronizadas novamente no Cloud Firestore.

<mark>Se você quiser se aprofundar um pouco mais no funcionamento deixo essa </mark> [<mark>playlist</mark>](https://www.youtube.com/watch?v=v_hR4K4auoQ&list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ) <mark> muito completa sobre o assunto.</mark>

### Modelo de dados do Cloud Firestore

O Cloud Firestore utiliza um modelo de dados NoSQL orientado a documentos, o que significa que ele difere de um banco de dados relacional SQL. Ao invés de tabelas e linhas, você armazena dados em documentos que posteriormente são organizados em coleções.

![Coleções contem vários documentos](https://cdn.hashnode.com/res/hashnode/image/upload/v1710988444587/42b5d498-e8db-42a7-80ec-788dbbef70f2.png align="center")

Veja alguns pontos-chave sobre o modelo de dados do Firestore:

* **Documentos:** Cada documento age como uma unidade de dados auto-contida, similar a uma linha em uma tabela relacional. Documentos contêm pares chave-valor, onde a chave é uma string que identifica o dado e o valor pode ser de diversos tipos como strings, números, booleanos, objetos, arrays, referências a outros documentos, entre outros.
    
    ![Exemplo de documento](https://cdn.hashnode.com/res/hashnode/image/upload/v1711244196562/1dc63c6e-3e81-4473-9de0-36244d41caac.png align="center")
    
* **Coleções:** Documentos relacionados logicamente são agrupados em coleções. Pense em coleções como pastas que organizam seus documentos. Por exemplo, você pode ter uma coleção "usuarios" para armazenar documentos de usuários do sistema, e cada documento representaria um usuário individual
    
* [![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711244386200/4397b99a-55dd-4c55-b7db-59a88742b0bb.png align="center")](https://firebase.google.com/docs/firestore/data-model?hl=pt-br)
    
* **Sem Esquema:** Firestore é schemaless, ou seja, não possui um esquema rígido que define a estrutura dos dados. Isso oferece flexibilidade, pois você pode adicionar novos campos a documentos existentes sem precisar modificar toda a coleção.
    
* **Hierarquia:** Documentos podem conter subcoleções, permitindo uma modelagem hierárquica dos seus dados. Por exemplo, a coleção "usuarios" poderia conter uma subcoleção "pedidos" para armazenar os pedidos realizados por cada usuário.
    

## Cloud Firestore X Realtime Database: qual devo usar?

O Realtime Database é um banco de dados que usa documentos JSON para armazenar os dados em pares de chave-valor enquanto o Cloud Firestore é uma versão mais recente do banco de dados que usa coleções de documentos para armazenar dados.

Existe algumas diferenças entre eles e abaixo vou citar 2 dos principais.

* **Modelo de Dados**:
    
    * **Realtime Database**: Utiliza um modelo de dados JSON em tempo real. Isso significa que os dados são sincronizados em tempo real entre todos os clientes que estão conectados ao banco de dados.
        
    * **Cloud Firestore**: Usa um modelo de dados de documentos coleções. Isso permite uma estrutura hierárquica mais flexível para dados, onde os documentos podem conter coleções aninhadas.
        
* **Preço**:
    
    * **Realtime Database**: O modelo de preços é baseado na largura de banda e no espaço de armazenamento utilizados.
        
    * **Cloud Firestore**: O modelo de preços é baseado na quantidade de leituras, gravações, exclusões e largura de banda utilizadas. Nesse caso um ponto de atenção ao modelar sua base 🚨🚨.
        

**OBS**. Qual usar depende muito das necessidades específicas do seu projeto:

* **Realtime Database** é uma boa escolha se você precisa de sincronização em tempo real de dados simples e tem requisitos de escalabilidade moderados.
    
* **Cloud Firestore** é preferível para aplicativos que requerem uma estrutura de dados mais complexa, consultas flexíveis e alta escalabilidade.
    

Em resumo, se você precisa de uma estrutura de dados mais flexível, consultas poderosas e escalabilidade robusta, o Cloud Firestore é provavelmente a melhor opção. Se você precisa de uma sincronização em tempo real simples e não tem requisitos de consulta muito complexos, o Realtime Database pode ser mais adequado.

Se você quiser se aprofundar um pouco mais nesse assunto acesse os links abaixo.

1. [https://firebase.google.com/docs/firestore/rtdb-vs-firestore?hl=pt-br](https://firebase.google.com/docs/firestore/rtdb-vs-firestore?hl=pt-br)
    
2. [https://stackoverflow.com/questions/46549766/whats-the-difference-between-cloud-firestore-and-the-firebase-realtime-database](https://stackoverflow.com/questions/46549766/whats-the-difference-between-cloud-firestore-and-the-firebase-realtime-database)
    

### Ativando o Firestore

Vamos utilizar o projeto **flutter-firebase-app** que criamos e configuramos no artigo passado, se você é novo [acesse aqui.](https://ianoliveira.dev/configurando-o-firebase-no-flutter)

1 - Vá até o console do Firebase e clique no seu projeto. No menu **Criação** , clique em **Firestore Database &gt; Criar banco de dados**.

![Ativação do cloud firestore](https://cdn.hashnode.com/res/hashnode/image/upload/v1711310955432/826163b4-e3da-4166-988e-68722fee48d4.png align="center")

2 - A próxima tela aparecerá solicitando que você selecione o local que será hospedado o banco de dados. Você pode usar o padrão mas fique certo que não poderá alterar o local posteriormente.  
Selecionar o local do banco de dados do Firestore durante a ativação é crucial por várias razões como:

1. **Latência**: Escolher um local próximo aos usuários finais reduz a latência e melhora a experiência do usuário.
    
2. **Conformidade**: Pode ser necessário garantir que os dados estejam armazenados em uma região específica devido a requisitos legais ou regulatórios.
    
3. **Resiliência**: Distribuir os dados em vários locais aumenta a resiliência da aplicação, garantindo disponibilidade mesmo em caso de falhas em um data center.
    
4. **Custo**: Escolher o local mais econômico pode reduzir os custos operacionais, já que alguns provedores de nuvem podem cobrar taxas diferentes com base na localização dos servidores.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711311251876/eadf4c4f-50dc-48e6-bcfa-8cd4047b2842.png align="center")

3 - Selecione **Iniciar em modo de teste** e leia a isenção de responsabilidade sobre as regras de segurança. O modo de teste garante que você possa gravar livremente no banco de dados durante o desenvolvimento. Em seguida, clique no botão **Criar** para prosseguir.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711311489053/95d1cd55-5c6a-4652-ae58-28fb658eb7d2.png align="center")

<mark>🚨 Para seus aplicativos, especialmente aplicativos de produção, é importante proteger seu banco de dados com regras de segurança. Para saber mais sobre regras de segurança, consulte </mark> [<mark>Regras de segurança do Firebase .</mark>](https://firebase.google.com/docs/rules?hl=pt-br)

### **Fazendo o CRUD no Firestore**

Adicione o package do cloud firestore no seu projeto

```yaml
dependencies:
  cloud_firestore: ^4.15.10
```

No Firestore teremos uma coleção chamada `todos` que seria usada para armazenar dados relacionados a cada um dos itens de tarefas que os usuários podem adicionar, editar ou remover.

Cada item consistirá nos seguintes campos (os tipos de dados são escritos entre colchetes):

* `title` (String): armazena o nome da tarefa pendente
    
* `createdAt` (Timestamp): data de criação da pendência
    
* `completed` (Booleano): se a tarefa pendente foi concluída
    
* `id`(String): identificador exclusivo de uma tarefa
    

No Dart, vamos desenvolver um objeto para manipular esses dados e facilitar a integração com o Firestore. No entanto, os dados são intercambiados com o Firestore no formato JSON. Portanto, nosso modelo deve incluir uma funcionalidade para converter os dados de e para um objeto Dart.

Para essa conversão, podemos implementar um método `toMap()` que será responsável por retornar um `Map` quando dado um objeto Dart, e também podemos utilizar um construtor de fábrica `fromJson` para facilitar a conversão de JSON para objetos Dart. Aqui está um exemplo de como isso pode ser feito:

Com esse código, você pode facilmente criar objetos `Todo` a partir de dados JSON usando o construtor `fromJson`, e também pode converter objetos `Todo` em um formato que pode ser armazenado ou transmitido como JSON usando o método `toMap()`.

```dart
import 'package:cloud_firestore/cloud_firestore.dart';

class TodoModel {
  String? id;
  final String title;
  final bool completed;
  final DateTime createdAt;

  TodoModel({
    this.id,
    required this.title,
    this.completed = false,
    required this.createdAt,
  });

  // Construtor Factory para converter JSON em objeto Dart
  factory TodoModel.fromJson(Map<String, dynamic> json) {
    return TodoModel(
      id: json['id'],
      title: json['title'],
      completed: json['completed'],
      createdAt: DateTime.fromMicrosecondsSinceEpoch(
        (json['createdAt'] as Timestamp).microsecondsSinceEpoch,
      ),
    );
  }
  // Método para converter objeto Dart em um Map
  Map<String, dynamic> toMap() {
    return {
      'title': title,
      'completed': completed,
      'createdAt': createdAt,
    };
  }

  TodoModel copyWith({
    String? id,
    String? title,
    bool? completed,
    DateTime? createdAt,
  }) {
    return TodoModel(
      id: id ?? this.id,
      title: title ?? this.title,
      completed: completed ?? this.completed,
      createdAt: createdAt ?? this.createdAt,
    );
  }
}
```

### Gravando dados

* [**Creat**](https://firebase.google.com/docs/rules?hl=pt-br)**e (Criar)**: No Firebase, você pode criar novos registros no banco de dados usando uma operação de escrita, como `.set()` ou `.add()` dependendo do tipo de banco de dados que você está usando.
    
    Ao usar `set()` para criar um documento, você precisa especificar um ID para ele. Exemplo:
    
    ```dart
    db.collection("cities").doc("new-city-id").set({"name": "Chicago"});
    ```
    
    No entanto, às vezes não há um ID significativo para o documento. É mais prático que o Cloud Firestore gere um automaticamente para você. Para fazer isso, podemos usar o `add()` na hora de criar o novo registro. Em nosso aplicativo de exemplo vamos utilizar-lo.
    
    **Adicionar uma tarefa (todo) (CRIAR):**
    
    O método **addTodo** é definido para adicionar uma nova tarefa à coleção ‘todos’ no Firestore. Veja o exemplo abaixo.
    

```dart
  Future<String> addTodo(TodoModel model) async {
     try {
          var snapshot = await firestore.collection('todos').add(model.toMap());
          return snapshot.id;
        } on FirebaseException catch (e) {
          debugPrint("Failed with error '${e.code}': ${e.message}");
          throw Exception(e.message);
        }
   }
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711910459592/9828ea05-8655-47f9-9135-cfa02219fb8b.gif align="center")

* **Read (Ler)**: Você pode ler dados do banco de dados usando operações de leitura, como `.get()` para Futures ou `.snapshots()` para Streams, que permitem recuperar dados de um caminho específico no banco de dados em tempo real.
    
    **Obtenha tarefas (LER):**
    
    O método **getTodos** é implementado para recuperar a lista de dados da coleção de '**todos**'.
    
    * ```dart
          Future<List<TodoModel>> getTodos() async {
              try {
                var snapshot = firestore.collection('todos').get();
                return snapshot.then(
                  (querySnapshot) {
                    return querySnapshot.docs.map((todo) {
                      var mapData = todo.data();
                      mapData['id'] = todo.id;
                      return TodoModel.fromJson(mapData);
                    }).toList();
                  },
                );
              } on FirebaseException catch (e) {
                debugPrint("Failed with error '${e.code}': ${e.message}");
                throw Exception(e.message);
              }
            }
        ```
        
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711910831452/6c832c69-8eca-471f-84f4-16ada08b4d38.png align="center")
    
* **Update (Atualizar)**: Para atualizar dados existentes no Firebase, você pode usar a função `.update()` para fazer alterações em campos específicos de um registro, ou a função `.set()` com a opção `merge: true` para mesclar dados novos e existentes.
    
    Para atualizar alguns campos de um documento sem substituir o documento inteiro, o `update()` é o ideial pois em nosso app atualizaremos o campo *completed* quando finalizarmos uma task em nosso aplicativo.
    
    É possível configurar um campo no documento como um carimbo de data/hora do servidor que detecta quando o servidor recebe a atualização basta usar o **FieldValue.serverTimestamp()**. Veja o exemplo abaixo.
    
    **Atualização de um todo (ATUALIZAÇÃO):**
    
    O método **updateTodo** é definido para atualizar um todo existente na coleção 'todos'. O método \*.\****doc(docId)*** : Obtém uma referência a um documento específico usando seu ID.
    
    ```dart
    Future<void> updateTodo(String id) async {
        try {
          var snapshot = firestore.collection('todos').doc(id);
          await snapshot.update({
            'completed': true,
            'updateAt': FieldValue.serverTimestamp(),
          });
        } on FirebaseException catch (e) {
          debugPrint("Failed with error '${e.code}': ${e.message}");
          throw Exception(e.message);
        }
      }
    ```
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711910595843/4f11bcce-2154-4506-9086-a0d6ed7897b6.gif align="center")
    
* **Delete (Excluir)**: Para excluir dados do Firebase, você pode usar a função `.delete()` para remover um registro específico do banco de dados.
    
    **Excluir todo (DELETE):**
    
    O método **deleteTodo** é implementado para excluir um registro da coleção ‘todos’.
    
* ```dart
           Future<void> deleteTodo(String id) async {
              try {
                final snapshot = firestore.collection('todos').doc(id);
                return await snapshot.delete();
              } on FirebaseException catch (e) {
                debugPrint("Failed with error '${e.code}': ${e.message}");
                throw Exception(e.message);
              }
            }
    ```
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711911007177/d4017e0c-cd6e-49f4-b40c-076ac8500751.gif align="center")

Essas operações CRUD são fundamentais para interagir com o banco de dados do Firestore e são usadas para criar, recuperar, atualizar e excluir dados em aplicativos desenvolvidos com o Firebase.

No nosso app de exemplo criamos uma classe chamada ***FirestoreService*** que fornece uma forma estruturada e modular de interagir com o banco de dados Firestore, implementando todas as operações necessárias.

Para mais informações sobre gerenciar dados no Firestore [clique aqui.](https://firebase.google.com/docs/firestore/manage-data/structure-data?hl=pt-br)

### Excluir coleções?

O Firebase não nos dá um método diretamente para excluir toda uma coleção de dados. Uma forma que temos de excluir uma coleção ou subcoleção completa no banco, seria recuperar (leia) todos os documentos dentro da coleção ou subcoleção e os excluir. Abaixo deixo um exemplo de como fazer isso 😎.

<mark>OBS: Esse processo gera custos de leitura e exclusão.</mark>

```dart
 Future<void> deleteAllData() async {
    try {
      final snapshot = await firestore.collection('todos').get();
      final List<DocumentSnapshot> docs = snapshot.docs;
      for (DocumentSnapshot doc in docs) {
        await doc.reference.delete();
      }
    } on FirebaseException catch (e) {
      debugPrint("Failed with error '${e.code}': ${e.message}");
      throw Exception(e.message);
    }
  }
```

Para explorar o código completo do aplicativo de exemplo e se aprofundar nos detalhes da implementação, você pode encontrar o projeto completo no [GitHub](https://github.com/iang12/firebase-flutter-example). Fique à vontade para acessar o repositório através do seguinte [link](https://github.com/iang12/firebase-flutter-example):

Minha recomendação é que você baixe o projeto e dê uma olhada em toda a implementação pois além de Firebase, usei coisas como [provider](https://pub.dev/packages/provider/example) e [change notifier](https://docs.flutter.dev/data-and-backend/state-mgmt/simple) para termos um aplicativo mais completo.

### **Conclusão**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711909968408/f295a1d1-37dc-4cd2-9a43-ef501195b200.gif align="center")

Bom é isso 😎.

Neste artigo, você aprendeu como utilizar o Firestore em um aplicativo real realizando operações na base de dados.

Em artigos futuros da série, veremos como usar outros recursos do Firebase como: Authentication, Crashlytics, Remote Config e muito mais com Flutter.

Espero que você tenha gostado e obrigado por acompanhar até aqui! Compartilhe-o com seus amigos e colegas!

Juntos, vamos construir apps incríveis que transformam o mundo!

Se tiver alguma dúvida ou contribuição, deixe nos comentários!

Me siga para estar sempre por dentro dos próximos artigos 📲 🚀

# **🌐 Minhas redes sociais 🌐**

[**GitHub**](https://www.github.com/iang12) | [**LinkedIn**](https://www.linkedin.com/in/ianoliveirag12/) | [**Instagram**](https://www.instagram.com/ianoliveira.dev/) | [**Twitter**](https://twitter.com/ianoliveirag12) | [**Medium**](https://medium.com/@ianoliveirag12)
