Introduction

Within this post I will provide a step by step guide to create a Google Cloud Firestore driven contact form for a Flutter web app; including details about how to build the form, validate the input and set up security rules before finally saving the data to the Google Firestore database.

Create A Contact Form

To create the form UI, the first step is to add Flutter Formbuilder to your project’s package dependency file.

flutter_form_builder: 3.8.0+1

Flutter Formbuilder is a popular package to build Material form fields and validation functions.

Next, update the project’s packages:

flutter pub get

Following that, import the package into your widget file.

import 'package:flutter_form_builder/flutter_form_builder.dart';

Then, declare a global key variable in which to store the form builder’s live state. This will be used to access the user’s input when the form is submitted.

final GlobalKey<FormBuilderState> _fbKey = GlobalKey<FormBuilderState>();

The form can now be constructed using a FormBuilder widget and the desired fields, along with labels and validators.

FormBuilder(
          key: _fbKey,
          autovalidate: true,
          child: ListView(
            children: <Widget>[
              FormBuilderTextField(
                attribute: "name",
                validators: [FormBuilderValidators.min(3),FormBuilderValidators.required()],
                decoration:
                InputDecoration(labelText:AppLocalizations(myLocale).translate('contactName')),
              )
              ,

              FormBuilderTextField(
                attribute: "email",
                validators: [FormBuilderValidators.email(),FormBuilderValidators.required()],
                decoration:
                InputDecoration(labelText: AppLocalizations(myLocale).translate('contactEmail')),
              )
              ,



              FormBuilderFilterChip(
                decoration: InputDecoration(labelText:AppLocalizations(myLocale).translate('contactQuery'),labelStyle: TextStyle(fontSize: 21,fontWeight: FontWeight.normal)),
                attribute: "category",
                options: [
                  FormBuilderFieldOption(
                      child: Text(AppLocalizations(myLocale).translate('mobile')),
                      value: "mobile"
                  ),
                  FormBuilderFieldOption(
                      child: Text(AppLocalizations(myLocale).translate('web')),
                      value: "web"
                  ),
                  FormBuilderFieldOption(
                      child: Text(AppLocalizations(myLocale).translate('vr')),
                      value: "vr"
                  ),
                  FormBuilderFieldOption(
                      child: Text(AppLocalizations(myLocale).translate('contactAR')),
                      value: "ar"
                  ),
                  FormBuilderFieldOption(
                      child: Text(AppLocalizations(myLocale).translate('contactWearable')),
                      value: "wearable"
                  ),
                  FormBuilderFieldOption(
                      child: Text(AppLocalizations(myLocale).translate('research')),
                      value: "research"
                  ),
                ],
              ),
              FormBuilderTextField(attribute: 'details',maxLines: 5,
                  validators: [FormBuilderValidators.required()]
                  ,
                decoration: InputDecoration(labelText: AppLocalizations(myLocale).translate('contactMessage')),
              )
            ],
          ),
        )

The above example code is taken from my homepage’s contact screen which can be seen below.

The Localization methods can be substituted with raw strings. In my example, I use a locale detection function to display labels in French and English. Validators are added in array format:

[FormBuilderValidators.email(),FormBuilderValidators.required()]

Setup Google Cloud Firestore

Setup a project in the Google Firebase Console and then navigate to the Database screen from the left hand menu.

Create a database. The next couple of screens prompt to choose between test or production followed by the desired server location. Choose production mode, as this will ensure that your database is write protected by default. Click Start collection and call the collection message.

Flutter Configuration

The next step is to initialise the local Flutter project for Firebase. To achieve this, the Firebase command line interface (CLI) must already be installed on the local machine.

In the command terminal, navigate to the project folder and execute the following command:

firebase use --add

Select the firebase project created earlier and provide a reference name like staging or production. This is required for deploying security rules while running the app locally during dev/testing.

Now, run the firebase intialisation command:

firebase init

The CLI app asks which features to set up. Select Firestore and Hosting. The remaining configurations can be left as default; apart from the final one which asks which folder to use as the public directory. This should be set to the folder where a local build will be deployed. For now, use build/web.

The above process should have created the following files in main project folder:

.filebaserc
firebase.json
firebase.indexes.json
firestore.rules

Open the firestore.rules file and set write permissions on the message collection.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /message/{document=**} {
      allow write;
    }
  }
}

Firebase enables SDK auto-configuration. This means it’s possible to use reserved URL’s that Google will automatically map to local files; including configuration details like API keys. To achieve this, a number of javascript references should be added to the project’s index.html file as follows:

<script src="/__/firebase/7.9.2/firebase-app.js"></script>
<script src="/__/firebase/7.9.2/firebase-auth.js"></script>
<script src="/__/firebase/7.9.2/firebase-firestore.js"></script>
<script src="/__/firebase/init.js"></script>

Once deployed to Google hosting or served locally for testing, the src values will be mapped to the relevant same-origin files including the app project’s Google configuration values (API keys, etc).

Connecting the Contact Form to Firestore

Add the following package to the project’s yaml file:

cloud_firestore: 0.13.4

Run flutter’s get CLI command to update packages (see further up).

Now, add the firestore package to the import section of the file where the contact form was created using the FormBuilder widget.

import 'package:cloud_firestore/cloud_firestore.dart';

Next, create a button within the widget tree using the following code example:

MaterialButton(
            child: Text('submit'),
            onPressed: () {
              if (_fbKey.currentState.saveAndValidate()) {
                Firestore.instance.collection('message').document()
                    .setData({ 
                  'name': _fbKey.currentState.value['name'],             
                  'email': _fbKey.currentState.value['email'],
                  'details': _fbKey.currentState.value['details'],
                  'category': _fbKey.currentState.value['category']
                });
              }
            },
          )

Here, the _fbKey variable, representing the form’s current state, provides the validated values of the user input from the form which can be saved to the message collection of the Firestore database.

Test Locally

Run the following command from the project’s local folder:

flutter build web

This will build a deployable version of the project to the build/web folder which was set during the firebase setup. Finally, run the build with the following command:

firebase serve

By default, the local server will be launched on port 5000. This can be accessed using http://localhost:5000 using a web browser.

Complete the web form and click submit.

If the form passes validation the data entered should now appear in the Firestore’s message collection, which can be viewed in the Google console.

Conclusion

Compared to Flutter Android/iOS app development, there are a couple of extra steps to take when connecting a Flutter web app to Google’s Cloud Firestore. Nevertheless, using the two packages described in this article and following the steps to set up a database within the Google console, it is a fairly simple process to create a basic contact form and persist the user input to the database.

Comments

  1. Kevin says:

    Hi Mat, thank you for this great tutorial. It has been very helpful. I am still new to flutter and I was wondering how I could add circularProgressIndicator and alert dialog to show “Message sent!”

Leave a Reply