A better approach for Cloud Firestore — ODM (Part 2)

Igor L Sambo💙🇲🇿
Flutter Community
Published in
5 min readJul 18, 2022

--

Setting Up and Playing with our Models

source: https://unsplash.com/photos/-7A6aRj_3nY

After knowing about Cloud Firestore ODM and its intent on structuring our way of interacting with the database, it’s time now to get a more in depth example on how it works.

For this article we will be learning how to define models and work with them, by implementing some concepts already exposed in the previous article as the FirebaseBuilder and code generation.

This article is part of a series of three related to Cloud Firestore ODM:

  1. A better approach for Cloud Firestore — ODM (Structuring Firebase NoSQL)
  2. A better approach for Cloud Firestore — ODM (Setting Up and Defining our Models)(this article)

Before defining Models

  1. To start working with ODM we need to add some dependencies, starting with an early version of dart:
environment:
sdk: ">=2.14.14 <3.0.0"

And as in any firebase project, some related dependencies, specifically for our example:

flutter pub add firebase_core
flutter pub add cloud_firestore
flutter pub add cloud_firestore_odm
flutter pub add json_annotation
flutter pub add --dev build_runner
flutter pub add --dev cloud_firestore_odm_generator
flutter pub add --dev json_serializable

2. We need also to initialize FlutterFire

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}

Past that, we can now focus on developing our applications, implementing Cloud Firestore ODM.

Defining Models

By defining models and implement ODM we ensure that:
1. We have a clear and exact representation of the data we expect to receive and modify from Firestore through our code;
2. Ensure validation of the data against a model, and if it’s not validated throw an error.

To put that in practice, we will consider a simple app where we save data from Minions and fetch back to have records of our thousands(ish) of minions being created. To do so, let’s begin by creating a model representing a minion.

Seeing the code, this is just a fancy model class with some extra elements, and all comes to the auto code generation that we discussed, being:

  • line 7: we have imported some packages that will actually make the job of defining this model according to Firestore ODM rules easier, and we can see the annotation and cloud_firestore_odm aligned for that purpose, and writing the key word part we’re declaring this code as an auto generated class dependent on the one we’re now and all the necessary boilerplate is then created.
  • line 24-25: for the matter of attaching this specific model to a collection I create a collection of the type, so that this specific reference allows firebase to communicate with the model.
  • line 9: you will also define your Json annotation so you can easily cast the data from cloud firestore to the app.

So, how is the code actually generated?

That’s actually the easy part and how Cloud Firestore ODM shows its power. In order to do that, just run the above command on the root of the project.

$ flutter pub run build_runner build — delete-conflicting-outputs

And while developing, if you are working on your model or any other class relying on auto code generation (has ‘part’ declaration), you can run once and it updates every time you change something on the file, by running the above command.

$ flutter pub run build_runner watch --delete-conflicting-outputs

With those steps done we have assured the two points we stated in the beginning, it’s now time to test and prove if it really throws an error.

Our Minions App
We can now implement the FirestoreBuilder and see how it responds to the implementation.

Seeing the code we can see some implementations that wouldn’t be common on a cloud firestore implementation, first being some specific references as MinionQuerySnapshot which is generated from the previous step — feel free to check the full example on github — and also the FirestoreBuilder that was explained on the previous article, let’s understand a bit more about this widget.

FirestoreBuilder
The main goal of a FirestoreBuilder is to listen to a reference, it beats the Stream/FutureBuilder for being specific on that goal and also caching the data created with the ref.snapshot, so, it will listen to the reference and build the widget based on the latest data coming from firebase.
So, we have three elements, being ref, child and builder, being the listened reference in our case the minionsRef created on our model class, an optional child created in order to have better performance around the reference and a function that builds widgets based on the latest snapshot from the reference we’re listening to, respectively.

If, perhaps we have some data on our database not “agreeing” with the model we created, it has to throw an error, let’s test it.

As we can see from the error, how easily we can get the object Minion from the snapshot and when the data isn’t right it indeed throws an error.

Better rebuilds
And to optimize our builds we can even use data selectors to specify which attributes from that reference we want to be listening, the implementation can be as follows:

FirestoreBuilder<String>(
ref: minionRef.doc(minionId).select((MinionDocumentSnapshot snapshot) => snapshot.data!.name)),
builder: (context, AsyncSnapshot<String> snapshot, Widget? child) {
return Text('Hi, this is ${snapshot.data}');
}
);

This way the widget will only be rebuilt if this specific attribute changes, so, if we change only this minion traits, we still good to not update this widget specifically.

And that it for this article…

Thank you for reading. I hope you enjoyed and learned from it.

Sign up to the mailing list to get notified for new articles drop.
For questions and/or suggestion, I am available through the comments section, email igorlsambo1999@gmail.com or twitter @lsambo02.

We catch up on the next article!!!

--

--