Flutter | Spin up a project with Firebase Emulator, Riverpod and Freezed

01kg - Sep 12 - - Dev Community

When you decide to build an app seriously, the considerations in your head is far more than the hello-world examples in getting-started docs.

How to create beautiful UI?
How to manage authentication in secure way? (Sign up, log in, log out)
How to store data in secure way?
How to manage states?
How to serialize and deserialize your data?
How to do routing?
How to do localization?
...

If we really wanna do a research of any of the above, it could be long enough to write a book.

Fortunately, there are templates, frameworks, packages that could do the heavy lifting things. But, unfortunately at the same time, they are scattered in the vast sea of Internet, you have to catch them and glue together.

Recently, I glued some of them and found they could work as a good starting point of my new apps.


PART I:

Flutter, the best development framework for my app.

What I am developing is an "App" with rich user-interaction, not a "Website" that full of content.

Flutter enables me to develop a web app that anyone can use it instantly with a browser, bypassed app stores, no need to download and install.

Install Flutter

Only 3 prerequisites are needed:

  1. Git. Go to Git official Site to download and install it.
  2. Visual Studio Code (VS Code). Go to VS Code official site to download an install it.
  3. Flutter extension for VS Code. Go to Flutter extension for VS Code page in VS Code Marketplace to install it.

Use only 1 line of command to make Flutter work

As Flutter's official site tells:

  1. In VS Code, Ctrl + Shift + P to call out Command Palette
  2. Type in Flutter
  3. Select Flutter: New Project. Follow the guidance.

*DO NOT MISS OUT THESE NOTIFICATIONs: *

Image description

Click Download SDK, then select an appropriate folder to store the SDK.

After the SDK downloaded, THIS NOTIFICATION IS IMPORTANT:

Image description

Click Add SDK to PATH. If succeed, another notification would prompt:

Image description

After that, if you still encounter the following notification, just ignore it:

Image description

Create a skeleton app

After you finished adding SDK, or you run Flutter: New Project again, it would let you choose what to create:

Image description

I prefer Skeleton Application since it follows some best practices.

If you encounter this prompt, just click yes:

Image description


PART II:

Firebase, the best solution of Authentication and Database for my app.

Only 3 prerequisites are needed:

  1. Node.js 16 or higher
  2. Java JDK 11 or higher
  3. Firebase CLI

Install Node.js

In Node.js's official site, it recommend to use fnm as the node manager.

  1. Install fnm with winget install Schniz.fnm
  2. IMPORTANT: for future convenience, add fnm environment configuration command to PowerShell's profile:

    # Check if $profile exists, if not create it
    if (-not (Test-Path $profile)) {
    New-Item -ItemType File -Path $profile -Force
    }
    
    # Check if the line already exists in $profile
    $lineToAppend = "fnm env --use-on-cd | Out-String | Invoke-Expression"
    if ((Get-Content $profile) -notcontains $lineToAppend) {
        Add-Content $profile $lineToAppend
    }
    
  3. Enable Powershell script running: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
    This allows Powershell to read and execute $profile. Or, you could get a "$profile cannot be loaded" error:
    Image description

  4. Let fnm install and use a nodejs version: fnm use --install-if-missing 20

  5. Open another Powershell window to confirm that nodejs is installed by:

    node -v # should print `v20.17.0`
    npm -v # should print `10.8.2`
    

    Install Java JDK

  6. Go to Java JDK official site download page

  7. Select "Ready for use JDK xx" to download and install

Install Firebase CLI

Open a Powershell window and run npm install -g firebase-tools


PART III:

Enable Firebase in your Flutter project

  1. Use VS Code to open your Flutter project (the skeleton project you created in PART I)
  2. Open Terminal (a Powershell window in VS Code) by ctrl + `
  3. firebase login This command launches a web browser window, prompting you to sign in to your Google account associated with Firebase.
  4. firebase init to setup Firebase in your project

         ######## #### ########  ######## ########     ###     ######  ########
         ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
         ######    ##  ########  ######   ########  #########  ######  ######
         ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
         ##       #### ##     ## ######## ########  ##     ##  ######  ########
    
    You're about to initialize a Firebase project in this directory:
    
      C:\Users\test\Documents\GitHub\test_2
    
    ? Are you ready to proceed? (Y/n) 
    

    Y is capital, means if you hit Enter, it assumes you input y and hit Enter

    ? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
     ( ) Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
     (*) Firestore: Configure security rules and indexes files for Firestore
     ( ) Functions: Configure a Cloud Functions directory and its files
    >( ) Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
     ( ) Storage: Configure a security rules file for Cloud Storage
     (*) Emulators: Set up local emulators for Firebase products
     ( ) Remote Config: Configure a template file for Remote Config
    (Move up and down to reveal more choices)
    

    I selected Firestore and Emulators. (Authentication is enabled by default)

    === Project Setup
    
    First, let's associate this project directory with a Firebase project.
    You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project.
    
    ? Please select an option:
      Use an existing project
      Create a new project
      Add Firebase to an existing Google Cloud Platform project
    > Don't set up a default project
    

    For my development purpose, I do not want to actually create a project in my Firebase Console and link it to this project.

    I just wanna fast develop my prototype using Firebase Emulator. If I wanna move the code into serious development, I will do firebase init again to create a project in Firebase Console, register my app and link it to my project.

    Here, I choose Don't set up a default project

    ? What file should be used for Firestore Rules? (firestore.rules) 
    

    Enter

    ? What file should be used for Firestore indexes? (firestore.indexes.json)
    

    Enter

    === Emulators Setup
    ? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
     ( ) Eventarc Emulator
     ( ) Data Connect Emulator
     ( ) Cloud Tasks Emulator
    >(*) Authentication Emulator
     ( ) Functions Emulator
     (*) Firestore Emulator
     ( ) Database Emulator
    (Move up and down to reveal more choices)
    

    I choose Authentication Emulator and Firestore Emulator

    ? Which port do you want to use for the auth emulator? (9099)
    

    Enter

    ? Which port do you want to use for the firestore emulator? (8080)
    

    Enter

    ? Would you like to enable the Emulator UI? (Y/n) 
    

    Enter

    ? Which port do you want to use for the Emulator UI (leave empty to use any available port)?
    

    Enter

    ? Would you like to download the emulators now? (Y/n)
    

    Enter

    +  Firebase initialization complete!
    

PART IV

Add packages and plugins to your Flutter project

Add Firebase packages to Flutter app

firebase_core package is required for any project:

flutter pub add firebase_core
Enter fullscreen mode Exit fullscreen mode

In PART III, I chose Authentication Emulator and Firestore Emulator, in addition to the required , I have to install these two corresponding packages: firebase_auth and cloud_firestore:

flutter pub add firebase_auth

flutter pub add cloud_firestore
Enter fullscreen mode Exit fullscreen mode

If you need to install more packages, head to this list and pick them.

Add riverpod, freezed packages and their supporting plugins

  1. Go to riverpod official site to learn how to install it
  2. Go to this page to learn what VS Code plugin is recommended to work with riverpod package.
  3. Go to freezed official page to learn how to install it.
  4. Go to this page to learn what VS Code plugin is recommended to work with freezed package.

PART V

Up and running

Run the Flutter app

  1. Press F5 to run Flutter app in dev mode: Image description

What I need is Chrome/Edge to run a Web app.

If I decide to develop a Windows/Android native app in future, I will install Visual Studio/Android Intelligence to support encoding.

This is the biggest advantage of Flutter: just focus on one code base! You could build any platform app as long as you put the code base in a corresponding environment!

Run the Firebase Emulators

firebase emulators:start --import=./firebase_emulators_backup --export-on-exit --project=demo-dummy
Enter fullscreen mode Exit fullscreen mode

Let's divide this line of command into parts for better understanding what's going happen:

  1. firebase emulators:start: This starts the Firebase emulators for the services defined in your firebase.json configuration file.

  2. --import=./firebase_emulators_backup: This option tells the emulators to import data from the specified directory (./firebase_emulators_backup). This is useful for preloading the emulators with a specific state.

  3. --export-on-exit: This option ensures that when the emulators are stopped, they will export their data to the specified directory. This is useful for saving the state of the emulators for future use.

  4. --project=demo-dummy: Specify a project ID starts with demo- tells emulated services to use a demo configuration and attempts to access non-emulated services for this project will fail. Remember I just wanna use emulators and did not actually create a real project in Firabase Console? This flag is important.

Emulators are running:

PS C:\Users\test\Documents\GitHub\test_2> firebase emulators:start --import=./firebase_emulators_backup --export-on-exit --project=demo-dummy
i  emulators: Starting emulators: auth, firestore
i  emulators: Detected demo project ID "demo-dummy", emulated services will use a demo configuration and attempts to access non-emulated services for this project will fail.   
i  firestore: Firestore Emulator logging to firestore-debug.log
+  firestore: Firestore Emulator UI websocket is running on 9150.
i  ui: Emulator UI logging to ui-debug.log

┌─────────────────────────────────────────────────────────────┐
   All emulators ready! It is now safe to connect your app. 
 i  View Emulator UI at http://127.0.0.1:4000/               
└─────────────────────────────────────────────────────────────┘

┌────────────────┬────────────────┬─────────────────────────────────┐
 Emulator        Host:Port       View in Emulator UI             
├────────────────┼────────────────┼─────────────────────────────────┤
 Authentication  127.0.0.1:9099  http://127.0.0.1:4000/auth      
├────────────────┼────────────────┼─────────────────────────────────┤
 Firestore       127.0.0.1:8080  http://127.0.0.1:4000/firestore 
└────────────────┴────────────────┴─────────────────────────────────┘
  Emulator Hub running at 127.0.0.1:4400
  Other reserved ports: 4500, 9150

Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
Enter fullscreen mode Exit fullscreen mode

PART VI

Implementing Riverpod and Freezed

Riverpod

Riverpod makes state management easier. (Why Riverpod?) Here I show you an example of how to manage Firebase's Auth state.

  1. Wrap the whole app with ProviderScope()

    ...
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    
    void main() async {
    runApp(
        const ProviderScope(
        child: MainApp(),
        )
    );
    }
    
  2. Wrap FirebaseAuth.instance.authStateChanges(); in a "stream provider".

    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:riverpod_annotation/riverpod_annotation.dart';
    
    part 'auth.g.dart';
    
    @riverpod
    Stream<User?> auth(AuthRef ref) {
        return FirebaseAuth.instance.authStateChanges();
    }
    

    Run dart run build_runner build in Terminal, auth.g.dart will be generated. This makes you write the fewest code.

  3. Listen to the provider

    ...
    import 'package:test_1/providers/auth.dart';
    
    class MainApp extends ConsumerWidget {
    const MainApp({super.key});
    
        @override
        Widget build(BuildContext context, WidgetRef ref) {
            final AsyncValue<User?> user = ref.watch(authProvider);
    
            return MaterialApp(
                home: user.when(
                    data: (user) {
                        if (user == null) {
                            return LoginPage();
                        } else {
                            return HomePage();
                        }
                    },
                    loading: () => const Scaffold(
                        body: Center(
                            child: CircularProgressIndicator(),
                        ),
                    ),
                    error: (error, stackTrace) {
                        return Scaffold(
                            body: Center(
                            child: Text('Error: $error'),
                            ),
                        );
                    },
                ),
            );
        }
    }
    

Freezed

Freezed makes serializing and deserializing really easy.

  1. ctrl + shift + P, input freezed, then select Freezed: Generate a New Freezed Class. Input a name, then select Yes if this Freezed Class should be serializable.

    I rely to this command. It creates a template as a starting point. (I input "dog" as the Freezed Class name)

    import 'package:freezed_annotation/freezed_annotation.dart';
    
    part 'dog.freezed.dart';
    part 'dog.g.dart';
    
    @freezed
    class Dog with _$Dog {
    factory Dog() = _Dog;
    
    factory Dog.fromJson(Map<String, dynamic> json) =>
                _$DogFromJson(json);
    }
    
  2. All I have to do is add attibutes between the parentheses of factory Dog() = _Dog;:

    ...
    factory Dog(
        {
            required String name,
            required int age,
            String? color,
        }
    ) = _Dog;
    ...
    
  3. Run dart run build_runner build in Terminal, a 19 lines long dog.g.dart is generated, and a 192 lines long dog.freezed.dart is generated. (I can't imagine how to do the hassle without the Freezed package.)

CONCLUSION

Thank you for reading! (Considering that we are already immersed in the era of short video consumption in social media platforms, it is really not easy to patiently read through such a long article.)

The tools I introduced are what I am using in my developing apps. Sure, they are not perfect.

If you wanna share some more awesome tools, leave a comment, or, better, write another long article and paste the link in comment!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .