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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Firebase Tutorial for Flutter: Getting Started
20 mins
- Getting Started
- Creating a Firebase Account
- Registering an iOS App
- Registering an Android App
- Creating Firestore Database
- Understanding Collections
- Creating the Model Classes
- Creating the Vaccination Model
- Creating the Pet Model
- Creating a DataRepository Class
- Using Streams
- Add DataRepository to Main
- Building the Pet Detail Page
- Where to Go From Here?
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:
- Define your fields. Name of the pet, notes and the type of pet.
- List of vaccinations for this pet.
- A reference to a Firestore document representing this pet.
- Constructor that pet name is required, the others are optional.
- A factory constructor to create a Pet from a Firestore DocumentSnapshot. You want to save the reference for updating later.
- A factory constructor to create a Pet from JSON.
- 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:
- Add a function to convert a map of key/value pairs into a Pet.
- Add another function to convert a list of maps into a list of vaccinations.
- Convert a Pet into a map of key/value pairs.
- 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:
- Your top level collection is called pets. Store a reference to this.
- Use the snapshots method to get a stream of snapshots. This listens for updates automatically.
- 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.
- 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.
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.
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:
And some images from the app:
Congratulations! You created both an iOS and an Android app that uses the Firestore database!