Using Firebase Firestore for a Flutter application is an excellent choice - but it bears some challenges. In this series, we introduce some best practices to help you ship your flutter app.
Apart from the inherent advantages of NoSQL databases and the Google Cloud Platform, the Cloud Firestore Plugin for Flutter gives its users a truly joyful way to interact with the service by just writing pure Dart code, leveraging all the syntactic sugar offered by the language.
As the plugin's example also shows, integrating Firestore into your application is as easy as:
- Defining a model class
- Declaring the functions to handle the conversion from document snapshots to model classes and vice-versa
(Assuming that you have a Firestore instance up and running, populated with some data)
Then all that remains is to open the stream of documents that you would like to access in your application. This sounds almost too good to be true, doesn't it? Well, sort of...
As your application grows and you need to deal with numerous model classes and operations to process them, it can be challenging to keep your code nice and clean.
Imagine that your application needs to handle various types of model conversions that differ in logic, but you would like to separate these concerns from the actual model class definitions.
It is also possible that you would like to attach Firestore-specific properties to your existing model classes without modifying them. A use-case for this would be to tie the Firestore document ID and the authenticated user's ID to an instance of a model class.
Let's assume that you are developing a Recipe app. You need to load recipe data from Firestore so that you can display your recipes. You also need to support writing recipes to Firestore so that your users can store their creations.
This is nothing extraordinary so far; you need to implement a fromMap and a toMap function to make this work.
Still, a few questions arise:
- How do you declare these functions to decouple them from the model class entirely?
- Given that your model class inherits from a base class, how do you access the fromMap and toMap methods without using the actual subclass type explicitly?
- How can you introduce support to other converters in your code later on without modifying existing code?
- How do you interact with the database layer of the app from the UI layer while respecting the Single-responsibility principle?
- How can you tie Firestore-specific properties to your existing model classes without modifying them?
In what follows, I will introduce an approach that answers all the questions above through a simple (yeah, you guessed it right) Recipe app. You are more than welcome to follow along with the code by cloning the 01-start branch of the companion repository.
The model of this app is kept extremely simple. We are dealing with a class to represent the authors of a recipe (Author), a class to encapsulate the recipe data (Recipe) and an empty abstract base class (BaseModel) to mark all the model classes as the descendants of the same parent.
At this point, the application is a conventional Flutter app without additional dependencies. It has two simple screens to display our model which is hard-coded in lib/models/dummy_recipes.dart.
The RecipesScreen is clean already at this point since it fulfils its sole responsibility of rendering a list of Recipe objects. However, the data is sourced from the DUMMY_RECIPES list, which is not a realistic solution in a real application.
The goal now is to extend this app to source the recipe data from Firestore in an isolated and reusable manner. That is exactly what we are going to explore in the next part of this series.