(Flutter) CRUD operations with Firestore database.
Tags: flutter, firebase, database, crud
Published: 2022-11-12 16:03:51

## Introduction
While building my app I found 100 ways to call data from the firestorm database, especially that there's still some outdated information in the internet. 
In this post I hope to give the clearest way to achieve all the crud operations especial the READ which for me took many tries until I got it right.

I'm assume already you set up the firebase and familiar with Flutter's syntax.

**Setup the model**
First I suggest you create the collection and a "demo" document inside Firestone just to grasp how the model we'll create after will look.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8sa4vscmdp166rxrokgy.png)


like you can see iv created the collection animals and inside there are a couple of fields. One particularly field is the "id" field, personally I like to create each document with the uniq id firebase automatically generates for it, this helps to avoid clashing. I create the field "id" so accessing the id would be easy, there are other ways to access it by calling the document id in the database but we'll not go over that in here. there are times that giving other types of id are preferable, for example when I create a "users" collection I prefer to keep the id's as their emails, this helps searching for the users especially if you group them later on in other collections.

Back to our collection "animals", now that I know what I what the object in my database to look like I'll create the model in flutter.

for that well create a new file `animals_model.dart`.
Inside well create a class for animals and list the requirements to create an "animals" object:
```dart
class Animal {
  String id; //id of the document inside firestore's db
  String name; // name of the animal
  int nubmer_of_legs; // nubmer of legs the animals has
  DateTime creation_date; // when the document was added tot he db

  Animal ({
    this.id = '',
    required this.name,
    required this.nubmer_of_legs,
    required this.creation_date,
  });}
```
This class will hold a model for object. the model will define all the necessary data that would be saved into Firestore. 
So now we can already create an object of type "animals" but we need to add it to the database. for this will make the `createAnimalDoc` method, but before that we need to be-able to convert the object data to json and back from json. 
```dart
  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
        'nubmer_of_legs': nubmer_of_legs,
        'creation_date': creation_date,
      };

  static Animal fromJson(Map<String, dynamic> json) => Animal(
        id: json['id'],
        name: json['name'],
        nubmer_of_legs: json['nubmer_of_legs'],
        creation_date: (json['creation_date'] as Timestamp).toDate(), // import 'package:cloud_firestore/cloud_firestore.dart';
      );
```
the `toJson()` takes the animal object data and puts it into json format so Firestone can understand, the `fromJson` maps all the fields and gets the data and places it into the model builder variable so we can access it in our code.


**CREATE**
after all this setup we can finally start the CRUD operations and well start with create.
to create a new document into firebase we simply create the method `createAnimalDoc` like the following:
```dart
  Future createAnimalDoc(String name, int nubmer_of_legs, DateTime creation_date) async {
    final newDocAnimal = FirebaseFirestore.instance.collection('groups').doc();
    final animal = Animal(
        name: name,
        id: newDocAnimal.id,
        nubmer_of_legs: nubmer_of_legs,
        creation_date: creation_date);
    await newDocAnimal.set(animal.toJson());
  }
```
In here we put all the variables into the function but if you use controllers you can also list them here if you used a form of some sort.
if i were to create a new document with a specific id would enter it inside the `...doc('ENTER ID');` 


**READ**
In my opinion read is the most versatile of the crud operations with flutter+firestore. this is because of all the different data you might request from the database. You obviously don't won't to call all the data you have but only whats relevant to your use case. ill list a few examples and how to solve/approach them in your code. 
before we start there's one thing to remember, to display that data into the page in flutter you must use the widgets `FutureBuilder()` or `StreamBuilder()`. Essentially you need to request the data and wait for in to arrive, those widgets are what can do that while giving you the ability to check if the data exists, or if there's an error or if it arrived and what to do with it.
the difference between `FutureBuilder()` and `StreamBuilder()` is that `FutureBuilder()` requests snapshot of the data once when the widget is called, waits for it and then displays it. the problem with it is if the data were to change it wont know it until it is called again (if you refresh the page or whatever). `StreamBuilder()` on the other hand requests a stream and continuously pips information to the page. so if the data changes in the database it will update inside the page too. 
What to use is judged by what you need. if one snapshot is enough and you dont expect the information to change then use `FutureBuilder()` as its lighter on the app and makes less calls to the database. if the information need to be updated real-time then `StreamBuilder()` is your guy.

lets see how to use is in our code, in this example ill use a `StreamBuidler()`:
first ill create an empty page
```dart
import 'package:flutter/material.dart';

class AnimalsPage extends StatefulWidget {
  const AnimalsPage({super.key});

  @override
  State<AnimalsPage> createState() => _AnimalsPageState();
}

class _AnimalsPageState extends State<AnimalsPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(),
    );
  }
}
```
now ill wrtie the call method that makes the 'pipe' to firestore:
```dart
    Stream<List<Animal>> fetchAnimals() {
    return FirebaseFirestore.instance
        .collection('animals')
        .snapshots()
        .map((snapshot) => snapshot.docs
            .map((doc) => Animal.fromJson(doc.data()))
            .toList());
  }
```

now ill set up `StreamBuilder()`
```dart
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: StreamBuilder<List<Animal>>(
          stream: fetchAnimals(), // feed our stream to the stream builder
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              final animals = snapshot.data!;
              return ListView.builder(
                itemCount: animals.length,
                itemBuilder: (context, index) {
                  return animalTile(animals[index]);
                }
                );
            } else if (snapshot.hasError) {
              return Text('Something went wrong!');
            } else {
              return Text('Something went wrong!');
            }
          },
        ),
      ),
    );
  }
```
as you can see if check if the snapshot's data exists, this is impotent for debugging.
and lastly we need to build the tile that will display the information. i like to do it with a separate widget in the same file:
```dart
Widget animalTile(Animal animal) => ListTile(
        title: Container(
          height: 50,
          width: 50,
          child: Text(animal.name),
        ),
      );
```
now we can request any information from the database regarding the collection animals.

Ok so how do we do it with a `futureBuilder()`? same way essentially:
```dart
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: FutureBuilder<List<Animal>>(
          future: fetchAnimals(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              final animals = snapshot.data!;
              return ListView.builder(
                  itemCount: animals.length,
                  itemBuilder: (context, index) {
                    return animalTile(animals[index]);
                  });
            } else if (snapshot.hasError) {
              return Text('Something went wrong!');
            } else {
              return Text('Something went wrong!');
            }
          },
        ),
      ),
    );
  }

  Future<List<Animal>> fetchAnimals() async {
    var collectionSnapshot =
        await FirebaseFirestore.instance.collection('animals').get();

    return collectionSnapshot.docs.map((doc) => Animal.fromJson(doc.data())).toList();
  }
```
the method `fetchAnimals()` needs to be asynchronous to wait for the snapshot to arrive from the database.

SPECIAL CASES:
- when you want to get specific document from the whole collection you can call .collection.doc(DOCUMENT_ID)

- if you want to search for documents that have a specific field you can use the `.where()` to achieve this, more information in the [official documentaion](https://firebase.google.com/docs/firestore/query-data/get-data).


**Update**
This one is pretty simple, you get in the instance of the document and then just call `.update()`
example:

`FirebaseFirestore.instance.collection('animals').doc('DOC_ID').update({
'FIELD1': 'NEW_DATA',
'FIELD2': 'NEW_DATA',
});`



**Delete**
Same with the update, you get in the instance of the document and then just call `.delete()`
example:
  `FirebaseFirestore.instance.collection('animals').doc('DOC_ID').delete();`


hopefully this give more current up to date information about the subject, keep in mind there are other ways to approach the CRUD functions with firestore this is just my way of doing it.

-T