Reading and Writing to Cloud Firestore with Flutter

Andy Julow

"February 8, 2020

Working with Cloud Firestore can save Flutter developers a lot of time over managing their own database and API, however getting started can often present a challenge. In this article we'll exame some of the more common use cases for Cloud Firestore in Flutter. A sample project is provided at the end of the article for trying out some of the concepts.

A Data Model

A key component to using Cloud Firestore in your Flutter project is a good model. Typically you will work with data in your application as a Dart object, however data is exchanged with Firestore in Json format. To facilitate in exchanging data between your Flutter application and Firestore, your model should not only define your properties and a constructor, but also provide a way to convert you data into and out of a Dart object.

  class Course {
    String courseId;
    final String name;
    final String instructor;
    final int capacity;

    Course({this.capacity,this.name,this.instructor,this.courseId});

    Map<String,dynamic> toMap(){
      return {
        'name' : name,
        'instructor':instructor,
        'capacity':capacity,
        'course_id':courseId,
      };
    }

    factory Course.fromJson(Map<String,dynamic> json){
      return Course(
        name: json['name'],
        capacity: json['capacity'],
        instructor: json ['instructor'],
        courseId: json ['course_id']
      );
    }
}

In this model, the toMap() function is responsible for returning a Map when presented with a Dart object, and the factory constructor Course.fromJson will handle receiving data from Firestore and constructing a Dart object from the data. The exchange from Map to Json and back again is handled for us under the hood.

Writing Data

Using Add

Records can be added to a collection with the add function. When using add, Firestore generates a unique document id which needs to be written to your record for future reference. The code below adds the course to the courses collection, awaits the returned document reference, and then uses the Firestore generated id to update the course_id field. Because the add function requires two writes to the database, it is often desirable to use the set function instead (see below).

    Future addCourse(Course course) async {
      var documentRef = await _db.collection('courses').add(course.toMap());
      var createdId = documentRef.id;
      _db.collection('courses').doc(createdId).update(
        {'course_id': createdId},
      );
    }

Using Set

The set function allows you to add a record with one line of code. When using set to add a new record, you must supply your own record id, which Firebase will then use as the document reference. In the example repo, the UUID package is used to generate an id, however you can use anything you like as long as it is unique for each record.

Set can also be used to update an existing record, however it is important to note that when updating using set in its default mode, existing data will be overwritten. This means if you fail to supply values for existing fields in the database, those fields will be lost. This behavior can be modified by supplying additional options, however to avoid confusion it is often best to use the update function if overwriting data is not desirable in your use case.

The code below will write the course to a record with a document reference that matches the course id. If no record exists it will be added, if a record does exist, it will be overwritten.

  Future setCourse(Course course) {
    _db.collection('courses').doc(course.courseId).set(course.toMap());
  }
  

Using Update

Update behaviors similar to an update with set, however any existing data that is not supplied in the update will be left alone. This may be desirable for all updates, but is especially useful for updating one or more fields for a record. The code below updates the instructor for a given course id, all other fields remain as is.

  Future updateInstructor(String courseId, String instructor) {
    _db.collection('courses').doc(courseId).update(
      {'instructor': instructor},
    );
  }
  

Reading Data

Get all records as Stream

    Stream> getAllStream() {
    return _db.collection('courses')
      .snapshots()
      .map((snapshot) => snapshot.docs
        .map((document) => Course.fromJson(document.data()))
        .toList());
    }
  

Get all records as Future

   Future> getAllFuture() {
    return _db.collection('courses')
    .get()
    .then((snapshot) => snapshot.docs
        .map((document) => Course.fromJson(document.data()))
        .toList());
    }
  

Get one record as Stream

    Stream getOneStream(String courseId) {
    return _db
        .collection('courses')
        .doc(courseId)
        .snapshots()
        .map((snapshot) => Course.fromJson(snapshot.data()));
    }
  

Get one record as Future

    Future getOneFuture(String courseId) {
    return _db
        .collection('courses')
        .doc(courseId)
        .get()
        .then((snapshot) => Course.fromJson(snapshot.data()));
    } 
  

Get all courses for instructor as Stream

   Stream> whereAsStream(String instructorName) {
    return _db
        .collection('courses')
        .where('instructor', isEqualTo: instructorName)
        .snapshots()
        .map((snapshot) => snapshot.docs
            .map((document) => Course.fromJson(document.data()))
            .toList());
    }
  

Get all courses for instructor as Future

   Future> whereAsFuture(String instructorName) {
    return _db
        .collection('courses')
        .where('instructor', isEqualTo: instructorName)
        .get()
        .then((snapshot) => snapshot.docs
            .map((document) => Course.fromJson(document.data()))
            .toList());
    }
  

Order all results by instructor

  Stream> orderByInstructor() {
    return _db
        .collection('courses')
        .orderBy('instructor', descending: true)
        .snapshots()
        .map((snapshot) => snapshot.docs
            .map((document) => Course.fromJson(document.data()))
            .toList());
  }
  

Limit number of results

  Stream> limitResults(int limitBy) {
    return _db.collection('courses')
    .limit(limitBy).snapshots()
    .map(
        (snapshot) => snapshot.docs
            .map((document) => Course.fromJson(document.data()))
            .toList());
  }
  

You can use the github repo down below to try out some of these options. You will need to add a firebase project to the repo which you can do by following this tutorial.