Simplyfying data access to Backend Services from a Flutter Application
How are Services used in Applications?
It works like a drive-through. The Service is always there like the person that hands you your food in the fast food restaurant of your choice. The UI requests the data from the Service whenever it needs data.
Like you, when you go to the drive through when you are hungry. Then the waiter receives your order and requests your order in the kitchen.
In this case the kitchen is your Backend, the Waiter (Service) is then “serving” the food(data) to you (UI). Receiving your menu choice is like getting the parsed data into the frontend.
Using Google Firebase makes it very easy to build smartphone applications from scratch because Google will handle the authentication and a basic database service for you.
The following article will describe how to create a Singleton Service which can be accessed from everywhere in your application handling your Firebase dependent data.
How to integrate Google Firebase into your Flutter Project:
I will not deep dive into that topic because Google already has a really good explanation.
Just make sure that the following dependencies are in your pubspec.yaml-file:
firebase_auth will enable the Firebase Authentication API for your application e.g. Basic Auth, Sign In with Google etc. The Firebase Service will later access the Firebase Realtime database. For that usage we will use the firebase_database package. firebase_core enables the Firebase Core API, which enables multi Core Apps from Firebase itself. For the basic implementation we will not call any of the Core Functions. But depending on the Application you develop it might be helpful to use some of those API’s.
The FirebaseService will be a Singleton. For the Singleton Service I decided to use the package get_it. The Singleton design pattern is a part of creational patterns and basically means that only one object of the class exists.
Why do we create a FirebaseService?
In general, while developing you will come to the point when the logic of your application growths.
Thinking about good programming, the way to go is the separation of UI (widgets) and logic. This will prevent failures and will also keep the logic consistent all over the application. This is also called Separation of Concerns.
Speaking explicitly of the Service we want to create here it would look like that:
The Separation of Concerns leads a bit into the topic Software Quality:
“Quality is never an accident; it is always the result of intelligent effort.”
Being a good software developer is also thinking about Software Quality. Two out of four measurement techniques for good software quality are Maintainability and Reliability.
For the achievement of Maintainability and Reliability the Separation of Concerns is very important.
For example, updating your Firebase database paths could happen to your project. First, maybe one of the keys for your data is named: lists.
Calling the data for that key from the Flutter application would look like that:
But then you decide that lists are maybe not the best name and rename it to: grocery_lists. So you would also have to rename any occurences in the frontend like this:
Without the separation of logic and widget you would need to check your whole application, rename every reference you are using and recheck the whole functionality.
Having it all in one service leading to just one file that has to be adapted enables code Maintainability!
Setting up the FirebaseService
After a bit of explanation we will now create our FirebaseService.
I really like to separate my code and give my project a clear folder structure.
So all widget files are placed in the package pages, our FirebaseService will be placed in the package service. I would also recommend to create a model package and place all your data model classes in there.
Create a new dart file in this package and name it “firebase_service.dart”.
Add the code below to the dart-File:
FirebaseAuth is going to handle your way of authentication which will later be shown in a coding example for sign in etc.
The FirebaseDatabase Instance will handle your data access to your Firebase database itself.
Now we switch to the main.dart-file. This is where our library get_it kicks in. Add the code below to your main.dart-file. Any other Singleton Service that you are going to create while coding can be registered here as well. Just do it the same way as with the FirebaseService.
Authentication in Firebase
Speaking about authentication and Firebase there are many options:
In this article signing in with E-Mail will be explained a bit. We will now extend the FirebaseService and add the signIn, signUp and signOut — methods:
After adding this methods either extend your login — page in the page package or create a login — page:
Getting data with Firebase
If you already have a page where you want to call the FirebaseService from, move to that page. Otherwise create a new page like the one below:
First things first. In this step we add the FirebaseService to the page and call the Singleton instance with get_it. I also added the MyExampleObject(later called GroceryList) to later assign the response from our FirebaseService.
But how do we get the data from the Service? Before we continue on the page we have to switch back to the FirebaseService. Below, our Authentication methods like signIn etc. add the following method:
Use the child() — Method to setup the correct database call for your project. This function will return a DataSnapshot, which is probably not the object that we want to access on our page. In my Application I wrote a factory that is parsing the DataSnapshot in the correct shape of the MyExampleObject. This approach looks like that:
If you choose this approach you can include the fromJson and toJson — methods as well. Therefore add the following dependencies to your pubspec.yaml:
I also have an article describing how to use the fromJson and toJson — methods with the Shared Preferences on your phone.
Switch back to the page that you want to fill with data from the database. Before integrating the data into our view, create a function to call the backend:
As already explained Firebase itself returns the data type DataSnapshot which has to be parsed in a class in the frontend. Of course you could also work with the value of the DataSnapshot which has the type Map<dynamic, dynamic>, but to be honest this would destroy the approach of good software quality. As we are working with Futures because of the async calls to the backend we have to add a FutureBuilder to the creation of the body part from our widget. Using the FutureBuilder will enable our widget to already set up the view. When the async call is finished our values that we receive from the backend will be set to the MyExampleObject (GroceryList). If we would use this without the FutureBuilder our MyExampleObject (GroceryList) would be null on the first call leading to an error in the widget creation.
The coding example below shows how to use the FutureBuilder and also how to display an loading indicator while the async calls are pending.
This article outlined how the design principle Separation of Concerns can be easily used to separate calls to the backend(logic) from the widgets(UI) which is also providing Software Quality to a project.
Besides that it was shown how to use the library get_it to create a Singleton Service, this approach can be used for all services in the Application.
All this knowledge was used to built a Singleton Service for simplyfying data access to Backend Services from your Flutter Application.