Flutter Provider Architecture with Firebase Full Guide — by building a working CRUD app

Ken Lee Chiaw Huat
Flutter Community
Published in
5 min readMar 15, 2020

Introduction

Ever since the creation of Flutter by the Google team few years back, I have been busy creating a numbers of Flutter projects, mostly in the aspect of mobile app. However, regardless of the state management that been used (ScopedModel, bloc, flutter_bloc, provider), most of the starting time was used to setting up the project folder structure, creating main key files such as theme, routes, models and services.

Instead of doing this repeatedly every time when creating a new Flutter projects, I’m going to spend some time with creating a boilerplate project that contains the minimum key folder structures, files, naming conventions and how to keep things better organised and maintainable using Provider package.

To make things more helpful, most of the Flutter projects that needed a backend is using Firebase and Cloud Firestore for user login/new user registration and CRUD capabilities, thus I added a simple Firebase user login using email/password and a simple notes CRUD features as part of the project.

Most of the times, is very common for any application to have user login and CRUD features. Thus, it makes more sense to added this to the boilerplate project for easier to understand and anyone can change from there.

So, let’s get started.

What are we building?

A boilerplate project for anyone that want to create a Flutter mobile application using Provider, Shared Preferences and Firebase.

What are the goals?

The main goal is to define a reference architecture that can be used as the foundation for Flutter app that require the usage of Firebase and eventually:

  • To reduce developer time in re-writing common code and project structure from scratch
  • To have a common folder and package structure that can be further extended should it require
  • Clearing define the app layer — UI layer, business logic layer and service layer

The 3 main layers:

  • UI layer — all UI widgets files
  • Business logic layer — contains all app business and presentation logic
  • Service layer — contains services needed to interact with between the app and 3rd party services such as APIs

This project bundled with a demo app as a practical example of this architecture. This is very important and useful for anyone to jump start right into the code and working app. From there, we can make modification as require for easily, faster and convenient.

The demo app is a working Todo app that comes with Firebase authentication using email and password and full CRUD capabilities.

The app demonstrates a simple but a comprehensive enough to capture the various challenges and use cases of state management across multiple screens and different modules.

Setting Up

As this guide is for creating a mobile app using the boilerplate project, first download or clone the following repo:

https://github.com/KenAragorn/create_flutter_provider_app.git

Then, follow the official guide in the above link and in less than few minutes, you will have the workable todo app that not just come with common codes that works for Firebase Authentication, but also all possible CRUD features that are commonly practise in any application that relied on Firestore as database.

Folder Structure

lib/|- constants/ — contains all application level constants for theme, font types and app strings.|- caches/ — contains data layer such as shared preferences.|- models/ — contains all the plain data models.|- services/ — contains all services files that handle the API calling for CRUD related functionalities.|- providers/ — contains all Provider models for each of the widget views.|- ui/ the main folder that will contains all UI related breaking down further by different modules (such as authentication, home, etc) and sub section (reusable widgets and screens).|- routes.dart|- auth_widget_builder.dart|- main.dart

What inside constants?

Basically, all needed constant files for the application to work will be here.

constants/
|- app_themes.dart – the theme file for the app
|- app_font_family.dart – the app global supported font family
|- app_strings.dart – the strings value used by the app

What inside cache?

As of now, only shared preferences files. In future we may have others such as sqlite features and it is good to keep things separated.

caches/
|- sharedpref/shared_preference_helper.dart

What inside models?

All plain data models file will be here. Depending on the number of the model files, if it is more than 10 files, we suggest separating the files by sub folder such as models/auth, models/todo etc. Else, keep it simple with every data model file is within this folder.

models/
|- user_model.dart
|- todo_model.dart

What inside services?

As we have backend firestore and firebase for application data such as user and todo data, we will need a dart classes that represent the CRUD services to handle any communication between the UI code and the backend.

services/
|- firestore_database.dart
|- firestore_service.dart
|- firestore_path.dart

What inside providers?

Contains all provider models needed for the app. The idea is like those in models and services in which, different provider class will be created for different key modules.

providers/
|- auth_provider.dart
|- theme_provider.dart

What inside UI?

This directory contains all the UI files for the application. As most of the times, there will be more than 1 files representing a screen, thus is always good to separate it by sub-folder and grouping it based on their key features or modules.

ui/
|- auth
|- register_screen.dart
|- sign_in_screen.dart
|- splash
|- splash_screen.dart
|- home
|- home.dart
|- todo
|- todos_screen.dart
|- create_edit_todo_screen.dart

Use Case: Firestore Service

Any widgets module can subsribe to updates from Firebase Firestore through streams, as well as write operation such as creation and deletion of data using Future-based APIs.

For convenient, all Firestore related CRUD services for the demo app is all in 1 place:

class FirestoreDatabase { // implementation omitted for brevity
Future<void> setTodo(TodoModel todo); // create / update
Future<void> deleteTodo(TodoModel todo); // delete
Stream<List<TodoModel>> todosStream(); // read
Stream<Job> todoStream({@required String todoId}); // read
}

With this setup, creating a widget that shows a list of jobs becomes simple:

@override
Widget build(BuildContext context) {
final database = Provider.of<FirestoreDatabase>(context, listen: false);
return StreamBuilder<List<Job>>(
stream: database.todosStream(),
builder: (context, snapshot) {
// TODO: return widget based on snapshot
},
);
}

Domain-level model classes are defined, along with fromMap() and toMap() methods for serialization.

Take note on stream-dependent services

When using Firestore, is quite common to organise user data inside documents or collections that depend on the unique user uid.

When reading or writing data, the app need to access to the user uid. This can change at runtime, as user can log out and sign back in with a different account, which eventually a different uid.

This, the FirestoreDatabase takes the uid as a constructor parameter. So, the moment we have FirebaseUser upon user logged in, we can just pass the uid to the Firestore database when performing CRUD operations.

To achieve this, FirestoreDatabase will be re-created everytime onAuthStateChanged changed.

I hope this started kit will helps you as how it helps me to minimize my work in the beginning and I also hope the above explanation can further assist you to understand the design behind it.

Cheers. :)

--

--

Ken Lee Chiaw Huat
Flutter Community

Technical Blogger | Mobile Developer@Flutter | Software Consultant | Father | Entrepreneur