Cloud Firestore - Creating Data from Your App

  1. Objectives
  2. Prerequisites
  3. Setup - new files
  4. Adding data to the Firestore from Flutter
    1. Adding Firestore libraries to a project
    2. Android changes
  5. Writing data to the Firestore programmatically
  6. Oh no - Security!
    1. Security Rules
  7. Finally, successfully creating data in Firestore
  8. Conclusion
  9. Completed code
  10. Other Resources

Objectives

Prerequisites

You must complete the Cloud Firestore - Introduction lab before this one.

Setup - new files

Put the following files in your lib/ folder:

Adding data to the Firestore from Flutter

Adding and viewing Firestore data through the web console is excellent for debugging and setting up some initial data to test. Of course, this is not scalable, and your app will be generating data you want to share and save. So you need to be able to add it programmatically from your Flutter app.

Fortunately, there is a library for that.

We will continue with the example application from the Firebase Authentication lab. Again, you must have authentication working in your app to read/write data to the Firestore.

Adding Firestore libraries to a project

First, we need to add the Cloud Firestore libraries to our project, and then run flutterfire config to ensure our app is configured to talk to the Firebase cloud properly.

  1. Open your firebase example project in VSCode.
  2. In the VS Code Terminal, run
     flutter pub add cloud_firestore
     flutterfire configure
     flutter pub get
    
  3. Use the arrow keys to select your example project from the list, then hit Enter.
  4. Hit Enter to keep the platform selection.
  5. Select ‘yes’ for if asked to overwrite ` lib/firebase_options.dart`. flutterfire config

Android changes

We have heretofore ignored the android/, ios/, etc. folders. We need to make a change to one of these folders now. Cloud Firestore libraries do not work with old Android versions. We need to specify a minimum Android version to avoid build errors.

We should now be good to write Firestore code and test it on an Android emulator.

Writing data to the Firestore programmatically

Our FirestoreAddScreen is necessarily a Stateful widget. Much like with Firebase Authentication, we need to make a call to the Firestore library asynchronously, the library does the actual data transmission to the Firestore for us, and then gives us a result (which could be an error), and we have to do something with the result.

In our example app, we want to create Person data and save it to the Firestore, much like we did through the web console.

How you create the raw data is up to you. You could get it from a Form, like we did on the LoginScreen. Maybe the data is calculated and produced by some algorithm.

For this example, we don’t really care how the data is created in our app. For simplicity’s sake, I have added the static method Person.getRandomPerson() to quickly generate a random Person instance with data that I will put in the Firestore.

You will notice the FirestoreAddScreen has an ElevatedButton that, when pressed, invokes a method called generateAndAddPerson(). It is in this method where we will create our data and send it to the Firestore. You can call Firestore methods from anywhere in your code, but in this case, we want to attach it to a button press.

Note following line at the top of firestore_add.dart:

import 'package:cloud_firestore/cloud_firestore.dart';

To call the Firestore programmatically, we need a reference to the Firestore collection (“People”) we want write to or read from. Add the following class variable to _FirestoreAddScreenState:

  // This reference allows us to work with our People collection in the Firestore.
  // The collection string must match the collection name in the Firestore exactly.
  final peopleRef = FirebaseFirestore.instance.collection('People');

Think of the peopleRef object as our communication path to the Firestore collection. It will handle network calls and all sorts of other things for us.

To write data to the Firestore collection, we need to call peopleRef.add(...). But what parameter do we give it? add() expects an instance of the Map class. Remember how I said that you should think of Documents in Firestore as similar to a Python dictionary or a Java map? Dart also has a notion of a Map which is a set of key-value pairs like in those other languages.

Creating a new Document in the Firestore is a matter of giving add() a map where the Map keys will be the Document’s fields, the Map values will be the Document’s fields’ values, and the Document’s field types are inferred from the Map values’ types.

Let’s add a convenience method to our Person class to return a Map version of a Person instance’s data. Paste the following method inside your Person class in person.dart:

  /// A utility method to convert our Person class into a Map
  /// with the same values. The Firestore library expects a Map.
  Map<String, Object?> toMap() {
    // The { } create a Map object, much like { } in Python creates a dictionary
    // This method creates and immediately returns the map.
    return {
      'first': first,
      'last': last,
      'age': age,
    };
  }

Aside: You may be asking yourself, why create a Person class at all? Why not just use a Map from the beginning to store data? A fair point. The main reason is that the Person object has nice named variables that can be type-checked, whereas Maps do not. The type-checking that comes with the Person class is very handy for things like auto-complete and making sure you’re referencing variables that actually exist. Embrace type checking. It helps you detect and prevent errors.

Okay, now, let’s add() our data. Change the generateAndAddPerson() method to the following:

  void generateAndAddPerson() async {
    try {
      Person p = Person.getRandomPerson();
      // This call transmits the data to the Firestore.
      // the 'await' call blocks execution until something comes back from the network.
      // the DocumentReference is a pointer to the Document in the cloud that was created.
      DocumentReference doc = await peopleRef.add(p.toMap());

      // Show a success message with the document ID.
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Success! Added ${doc.id}')),
      );
    } on FirebaseException catch (e) {
      // Something went terribly wrong
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Something went wrong! ${e.message}')),
      );
      print(e);
    }
  }

There are a few things of note in this code:

Oh no - Security!

When you click the button, you will get an error pop-up. You will also get an ugly exception in your Debug Console.

Denied

This is a common omission people make. When we created our Firestore database in the console, we put it in Production mode. This denies access by default. That’s a good thing actually!

Security Rules

Let’s tell Firestore we want people who are signed into our app to be able to read and write from the database.

  1. Click on the “Rules” tab.
  2. In the text area, change the contents to the following:
     service cloud.firestore {
     match /databases/{database}/documents {
         match /{document=**} {
         allow read, write: if request.auth != null;
         }
       }
     }
    
  3. Click the “Publish” button.

We are now good to go. Users logged into our app can read and write data to the Firestore, but anonymous internet strangers from other apps and websites cannot. You can read more about security rules here.

Finally, successfully creating data in Firestore

Open your Firestore database in the web console. You should now see several new entries with new data! One new entry for every time you click the button.

Try this: Have the web console visible while you press the button in your emulator or phone. You should see the new data appear in real time in the web console!

Firestore with added data

Conclusion

Congratulations! Your app is now saving data to the cloud. You now have the capacity for your app’s users to create and share data with one another because anyone using your app will be connected to your Firestore. What’s more, you could have a million users all writing data at once and the Firestore can handle it, and you do not have to change a single line of code. Of course, Google will start charging you a hefty sum for that!

I know that was a lot of setup, but the actual adding of data is only a few lines of code. The rest of the setup will make expanding on our functionality much easier.

Right now, we’re just adding data. In the next lab, we will demonstrate how to display data from the Firestore.

Completed code

Other Resources