Firebase Tutorial for Flutter: Getting Started

In this tutorial, you’ll learn how to use Firebase Firestore databases in Flutter by creating a fun app that will help you take care of your pets. By Kevin D Moore.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Creating the Pet Model

Now, right-click the models folder and choose New ‣ Dart File. Name the file pets. Add the following:

class Pet {
  // 1
  String name;
  String notes;
  String type;
  // 2
  List<Vaccination> vaccinations = List<Vaccination>();
  // 3
  DocumentReference reference;
  // 4
  Pet(this.name, {this.notes, this.type, this.reference, this.vaccinations});
  // 5
  factory Pet.fromSnapshot(DocumentSnapshot snapshot) {
    Pet newPet = Pet.fromJson(snapshot.data);
    newPet.reference = snapshot.reference;
    return newPet;
  }
  // 6
  factory Pet.fromJson(Map<String, dynamic> json) => _PetFromJson(json);
  // 7
  Map<String, dynamic> toJson() => _PetToJson(this);
  @override
  String toString() => "Pet<$name>";
}

Here you have:

  1. Define your fields. Name of the pet, notes and the type of pet.
  2. List of vaccinations for this pet.
  3. A reference to a Firestore document representing this pet.
  4. Constructor that pet name is required, the others are optional.
  5. A factory constructor to create a Pet from a Firestore DocumentSnapshot. You want to save the reference for updating later.
  6. A factory constructor to create a Pet from JSON.
  7. Turn this pet into a map of key/value pairs.

Next, below the class add:

// 1
Pet _PetFromJson(Map<String, dynamic> json) {
  return Pet(
    json['name'] as String,
    notes: json['notes'] as String,
    type: json['type'] as String,
    vaccinations: _convertVaccinations(json['vaccinations'] as List)
  );
}
// 2
List<Vaccination> _convertVaccinations(List vaccinationMap) {
  if (vaccinationMap == null) {
    return null;
  }
  List<Vaccination> vaccinations =  List<Vaccination>();
  vaccinationMap.forEach((value) {
    vaccinations.add(Vaccination.fromJson(value));
  });
  return vaccinations;
}
// 3
Map<String, dynamic> _PetToJson(Pet instance) => <String, dynamic> {
      'name': instance.name,
      'notes': instance.notes,
      'type': instance.type,
      'vaccinations': _VaccinationList(instance.vaccinations),
    };
// 4
List<Map<String, dynamic>> _VaccinationList(List<Vaccination> vaccinations) {
  if (vaccinations == null) {
    return null;
  }
  List<Map<String, dynamic>> vaccinationMap =List<Map<String, dynamic>>();
  vaccinations.forEach((vaccination) {
    vaccinationMap.add(vaccination.toJson());
  });
  return vaccinationMap;
}

Here you:

  1. Add a function to convert a map of key/value pairs into a Pet.
  2. Add another function to convert a list of maps into a list of vaccinations.
  3. Convert a Pet into a map of key/value pairs.
  4. Convert a list of vaccinations into a list of mapped values.

Now that you’ve added the classes to hold your data, you need to add a way to retrieve and save it.

Creating a DataRepository Class

Next, you’ll create a DataRepository class, which retrieves and saves your data. You need to isolate your usage of Firebase as much as possible to follow Android best practices.

First, right-click the lib directory and select New ‣ Directory. Then name the directory repository.

Next, right-click the models folder and choose New ‣ Dart File. Name the file dataRepository and add the following:

class DataRepository {
  // 1
  final CollectionReference collection = Firestore.instance.collection('pets');
  // 2
  Stream<QuerySnapshot> getStream() {
    return collection.snapshots();
  }
  // 3
  Future<DocumentReference> addPet(Pet pet) {
    return collection.add(pet.toJson());
  }
  // 4
  updatePet(Pet pet) async {
    await collection.document(pet.reference.documentID).updateData(pet.toJson());
  }
}

Here’s what you added:

  1. Your top level collection is called pets. Store a reference to this.
  2. Use the snapshots method to get a stream of snapshots. This listens for updates automatically.
  3. Add a new pet. This returns a Future if you want to wait for the result. Note that add will automatically create a new document id for Pet.
  4. Update your pet class.

You’ve added classes to hold, retrieve and save your data. Now you need to add a way to update your lists when different uses add new data.

Using Streams

Streams are a sequence of asynchronous data that sends when ready. Firestore sends updates to your list of pets when someone else adds or modifies a pet.

You’ll use a stream in main.dart to listen for the list of pets. When a user adds a new pet or updates a pet, the stream redraws the list with the updated information.

Add DataRepository to Main

First, open main.dart. There are //TODO... items throughout the code where you need to add your pet and data repository code.

In _HomeListState add:

final DataRepository repository = DataRepository();

This gives access to Firestore throughout this class. Then, in the _buildHome, replace body with:

body: StreamBuilder<QuerySnapshot>(
  stream: repository.getStream(),
  builder: (context, snapshot) {
    if (!snapshot.hasData) return LinearProgressIndicator();
    return _buildList(context, snapshot.data.documents);
  }),

The StreamBuilder first checks to see if you have any data. If not, it’ll show a progress indicator. Otherwise, it’ll call _buildList.

Now go to _buildList and replace children: [] with:

children: snapshot.map((data) => _buildListItem(context, data)).toList(),

This code maps the list from data, creates a new list item for each one and turns that into a list that the children parameter needs.

Next, go to the //TODO Add New Pet to repository above _buildList in the onPressed() and add:

Pet newPet = Pet(dialogWidget.petName, type: dialogWidget.character);
repository.addPet(newPet);

This code creates a new Pet class and uses the repository to add the new Pet. When you build and run the app, notice that the list automatically updates without you having to write any code for it.

Now, go to _buildListItem and change the method to:

  
Widget _buildListItem(BuildContext context, DocumentSnapshot snapshot) {

This code adds the snapshot parameter.

Next, go to // TODO Get Pet from snapshot and add:

final pet = Pet.fromSnapshot(snapshot);
if (pet == null) {
  return Container();
}

This creates a Pet class from the snapshot passed in. Do a null check to make sure it created a pet.

Now, go to // TODO add pet name and show the pet name replacing the expanded widget with:

Expanded(child: Text(pet.name == null ? "" : pet.name, style: BoldStyle)),
_getPetIcon(pet.type)

Build and run the app to make sure it compiles.

App main screen

Try clicking the floating action button to enter a pet name and type. Press add and make sure a new item appears.

If you see any errors, make sure you have the Firestore database setup and that you added all of your Google files.

Next go to // TODO add pet and pass your pet class to PetDetails:

builder: (context) => PetDetails(pet),

Nice job! Now it’s time for the Pet Detail screen.

Building the Pet Detail Page

First, open PetDetails.dart and add the pet field and constructor:

final Pet pet;
const PetDetails(this.pet);

Then import the Pet class and change the title to:

          
title: Text(pet.name== null ? "" : pet.name),

Next, add the pet field and constructor to PetDetailForm class:

  
final Pet pet;
const PetDetailForm(this.pet);

Then, in _PetDetailFormState.initState, set type to the pet’s type:

type = widget.pet.type;

Next, in the build method, find the first initialValue and replace it with:

initialValue: widget.pet.name,

This build method uses the FormBuilder library to create a column of form fields that have validation built in. You can find more information at: Flutter FormBuilder .

Next, in the notes field, replace the initialvalue with:

initialValue: widget.pet.notes,

Now find the FormBuilderCustomField entry. Go to // TODO use vaccination count and // TODO Pass in vaccination and replace that code with:

itemCount: widget.pet.vaccinations == null ? 0 : widget.pet.vaccinations.length, itemBuilder: (BuildContext context, int index) {
  return buildRow(widget.pet.vaccinations[index]);
},

This uses the vaccination list to get the count. It creates a new row for each vaccination.

Next, in the FloatingActionButton section, replace _addVaccination with:

_addVaccination(widget.pet, () {

You want to pass in the pet, so you’ll need to update this method later.

Now go to the next // TODO Update widget and add:

widget.pet.name = name;
widget.pet.type = type;
widget.pet.notes = notes;
repository.updatePet(widget.pet);

This code uses the variables set in the fields and updates the pet. It also updates the stream, so when you return to the list, it is already updated.

Next, replace buildRow() with:

Widget buildRow(Vaccination vaccination) {

Then, import Vaccination if it hasn’t already imported. Replace the rest of the method with:

    
return Row(
      children: <Widget>[
        Expanded(
          flex: 1,
          child: Text(vaccination.vaccination),
        ),
        Text(vaccination.date == null ? "" : dateFormat.format(vaccination.date)),
        Checkbox(
          value: vaccination.done == null ? false : vaccination.done,
          onChanged: (newValue) {
            vaccination.done = newValue;
          },
        )
      ],
    );

This creates a row with the vaccination name, date and checkbox.

Now update _addVaccination to take a pet:

void _addVaccination(Pet pet, DialogCallback callback) {

Finally, go to the very bottom of the file and add the following (replace TODO):

Vaccination newVaccination = Vaccination(vaccination, date: vaccinationDate, done: done);
if (pet.vaccinations == null) {
  pet.vaccinations = List<Vaccination>();
}
pet.vaccinations.add(newVaccination);

This creates a new Vaccination and adds it to your vaccination list. It uses the callback so the caller can update the state and have the UI update.

Build and run the app in either iOS or Android and make sure everything works. Don’t assume that both work as they have different Firestore setups.

App main screen

Try adding any pets you have as well as any vaccinations and check the Firestore console to see what the data looks like. This is an example of some data in Firestore:

Pets collection with stored documents and fields.

And some images from the app:

Main page of app now showing Batman, Sassy and Sassy 2 as pets.

Dialog box to add a pet, Saddy the cat.

Sassy's file with options to add notes and vaccinations.

Congratulations! You created both an iOS and an Android app that uses the Firestore database!