If you're familiar with object-oriented programming concepts you should be able to complete this codelab. You don't need previous experience with Dart, mobile programming, or Firebase, although completing an introductory Flutter codelab first will be helpful.

Flutter and Firebase work hand-in-hand to help you build mobile apps in record time. Flutter is Google's SDK for building mobile apps for iOS and Android. Firebase gives you access to backend services for mobile applications — including authentication, storage, database, and hosting — without maintaining your own servers.

In this codelab, you'll learn how to create a Flutter app that uses Firebase. The app helps new parents choose baby names by letting friends and family vote for their favorites. Specifically, the app accesses a Cloud Firestore, and allows the user to update the Cloud Firestore using a transaction.

Here's what the final app will look like, on both iOS and Android. Tapping a name adds your vote.

Here's a short video that demonstrates building a similar app in real time. It provides a good overview of the steps you will encounter in this codelab.

What is your level of experience with building mobile apps?

Never built mobile apps Built apps for the mobile web only Built apps for Android only Built apps for iOS only Built apps for Android and iOS Built apps for mobile web, Android, and iOS

You need two pieces of software to complete this lab:

You can run this codelab using one or more of the following devices:

Follow the Get Started: Test Drive guide to create a new Flutter app. Name the app baby_names instead of myapp. The instructions differ depending on your editor. If you're using an IDE, a new app is usually called a project.

In this codelab, you'll be editing lib/main.dart.

  1. If you have a Firebase account, sign in to it. If not, you need to create an account. A free plan will be sufficient for this codelab (and most development).
  2. In the Firebase console, click on Add project.
  3. Name your project (for example, "baby names app db") and set the Country/region to the location of your company or organization. Click Create Project.
  4. After a minute your project will be ready. Click Continue.

Once you have created a Firebase project, you can configure one (or more) apps to use that project. This is done by registering your app's platform-specific ID with Firebase and generating configuration files for your app.

If your Flutter app will be released on both iOS and Android, you need to register both the iOS and Android versions within the same Firebase project. If not, just skip the unneeded section.

In the top-level directory of your Flutter app, there are subdirectories called ios and android. These directories hold the platform-specific configuration files for iOS and Android, respectively.

Configure iOS

  1. In the Firebase console, be sure Project Overview is selected in the left nav. Click Add Firebase to your iOS app to get this:

    The main thing you need to fill in is the iOS bundle ID, which you will determine in the following two steps. (More information about bundle IDs).
  2. In a terminal window, go to the top-level directory of your Flutter app and run the command open ios/Runner.xcworkspace to open Xcode. (More information on Xcode property lists).
  3. In Xcode, click the top-level Runner in the left pane to show the General tab in the right pane, as shown in the figure below. Copy the Bundle Identifier.


  1. In Firebase, paste the Bundle Identifier into the iOS bundle ID field, shown in the figure above. (The actual values of "yourcompany" and "yourproject" depend on what you named your Flutter app.)
  2. In Firebase, click Register App.
  3. Continuing in Firebase, follow the instructions to download the config file GoogleService-Info.plist.
  4. In Xcode, notice that Runner has a subfolder also called Runner, as shown in the figure above. Drag the GoogleService-Info.plist file you just downloaded into that Runner subfolder. In the Choose options for adding these files dialog that appears, click Finish.
  5. In Firebase, click Next and skip the remaining steps and go back to the main page of the Firebase console.

Configure Android

  1. In the Firebase Console, be sure Project Overview is selected in the left nav. Click Add Firebase to your Android app. If you have already added an app (for example, the iOS app from the preceding section), click Add another App to get to this.
  2. In your Flutter app directory, open the file android/app/src/main/AndroidManifest.xml. The string value of the manifest element's package attribute is the Android package name (something like com.yourcompany.yourproject). Copy this value.
  3. In the Firebase console, paste the copied package name into the Android package name field. (More information about package and application IDs).
  4. Optional: If you plan to use Google Sign In or Firebase Dynamic Links, you need to fill In the Debug signing certificate SHA-1 field. Follow the instructions in the Authenticating Your Client guide to get the debug certificate fingerprint value and paste it in.
  5. Click Register App.
  6. Follow the instructions to download the file google-services.json. Move the google-services.json file into the android/app directory of your Flutter app.
  7. In Firebase, skip the remaining steps and go back to the main page of the Firebase console.
  8. Finally, you need the Google Services Gradle plugin to read the google-services.json file that was generated by Firebase. In your IDE or editor, open android/app/build.gradle, and add the following line as the last line in the file:
apply plugin: 'com.google.gms.google-services'

In android/build.gradle, inside the buildscript tag, add a new dependency:

buildscript {
   repositories {
       // ...
   }

   dependencies {
       // ...
       classpath 'com.google.gms:google-services:3.2.1'   // new
   }
}

The "new" comment is just to indicate the line that was added. Make sure the new dependency lines up exactly with the other classpath. This line specifies version 3.2.1 of the plugin (do not use version 3.3.0 or 3.3.1 at this point). For more information see the Add the SDK section of the Add Firebase to Your Android Project documentation (but don't follow those instructions as they are different from the Flutter instructions).

FlutterFire plugins

Your Flutter app should now be connected to Firebase.

Flutter provides access to a wide range of platform-specific services, including Firebase APIs, using plugins. Plugins include platform-specific code to access services and APIs on iOS and Android.

Firebase is accessed through a number of different libraries, one for each Firebase product (for example, databases, authentication, analytics, storage). Flutter provides a set of plugins to access each Firebase product, collectively called FlutterFire. Be sure to check the FlutterFire GitHub page for the most up-to-date list of FlutterFire plugins.

Your setup is finished and you are ready to start building your app. You'll start by setting up Cloud Firestore and initializing it with some values.

  1. Open the Firebase console, and select the project you created during setup.
  2. Under Develop, select Database.
  3. In Cloud Firestore (currently "Cloud Firestore Beta") select Get Started.
  4. In the Security rules for Cloud Firestore dialog, select Start in test mode and click Enable.
  1. Our database will have one collection, named "baby", where the names and votes are stored. Click Add Collection, set the collection name to baby and click Next.
  2. You can now add documents to your collection. Each document contains a Document id, and name and votes fields.

    Rather than auto-generating the Document id, use the baby name in all lowercase. This will ensure the documents will be displayed alphabetically by baby name. (By default, the document ids are timestamps, so documents are displayed in the order they were created.) Add a second field for the number of votes by clicking Add Field. Make sure you set the type of this field to number and initialize it to zero. Click Save.
  3. Add additional baby names by clicking on Add Document.
  1. In your IDE or editor, open the file pubspec.yaml. Add a dependency for cloud_firestore and save the file.
dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^0.7.3     # new

You can get the current version number of the Cloud Firestore plugin from pub. (More information about pub dependencies).

  1. In your IDE (or a terminal whose current directory is your flutter app directory) run flutter packages get. If you get an error, make sure your indentation is exactly as shown above, using two spaces (and not tabs).
  1. Using your IDE or editor, open lib/main.dart. This currently contains the default Flutter app. Delete all of the code, and replace with the following:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Baby Names',
      home: const MyHomePage(title: 'Baby Name Votes'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text(title)),
      body: new StreamBuilder(
          stream: Firestore.instance.collection('baby').snapshots(),
          builder: (context, snapshot) {
            if (!snapshot.hasData) return const Text('Loading...');
            return new ListView.builder(
              itemCount: snapshot.data.documents.length,
              padding: const EdgeInsets.only(top: 10.0),
              itemExtent: 25.0,
              itemBuilder: (context, index) {
                DocumentSnapshot ds = snapshot.data.documents[index];
                return new Text(" ${ds['name']} ${ds['votes']}");
              }
            );
          }),
    );
  }
}

The first half of this program is fairly straightforward. You've imported cloud_firestore.dart, a plugin for communicating with the Firestore. You've also set the app's title to Baby Names and the home page title to Baby Name Votes.

The interesting part of this program is in the body of the Scaffold, which is a StreamBuilder widget. A stream builder takes two arguments: an input stream and a builder function. The stream provides an asynchronous sequence of the documents in the ‘baby' collection in the Firestore, including changes over time. When any user of the app votes for a baby name, Firestore propagates the change so that all users will see the updated value.

The StreamBuilder builder returns a ListView widget, which contains a list of baby names. For now, each item in the ListView is just a line of text containing the values for name and votes.

  1. Run the app. You've just read from the database you created!

Next you will create some layout for the list items and add the ability to tap on a list item to vote for it.

  1. Replace the itemExtent and itemBuilder arguments to the ListView.builder constructor with the following code:
itemExtent: 55.0,
itemBuilder: (context, index) =>
   _buildListItem(context, snapshot.data.documents[index]),

Instead of creating each list item inline, this calls a private method _buildListItem.

  1. Add the private method to the MyHomePage class, just after the line final String title;.
Widget _buildListItem(BuildContext context, DocumentSnapshot document) {
  return new ListTile(
    key: new ValueKey(document.documentID),
    title: new Container(
      decoration: new BoxDecoration(
        border: new Border.all(color: const Color(0x80000000)),
        borderRadius: new BorderRadius.circular(5.0),
      ),
      padding: const EdgeInsets.all(10.0),
      child: new Row(
        children: <Widget>[
          new Expanded(
            child: new Text(document['name']),
          ),
          new Text(
            document['votes'].toString(),
          ),
        ],
      ),
    ),
    onTap: () => Firestore.instance.runTransaction((transaction) async {
          DocumentSnapshot freshSnap =
              await transaction.get(document.reference);
          await transaction.update(
              freshSnap.reference, {'votes': freshSnap['votes'] + 1});
        }),
  );
}

The layout for the list items draws a border with rounded corners around the item, adds some padding, and right-justifies the value of votes.

The ListTiles are assigned a key so their identity can be determined during updates. The keys are generated from the documentID using the ValueKey function, which requires the document IDs be unique. If you augment the app so users can add names to the list, you would add error checking to make sure the names (and thus the documentIDs) are unique. Assigning keys to items in a mutable list is good practice.

The onTap handler is more interesting. It uses a Cloud Firestore transaction, which guarantees that the function inside the transaction is executed atomically. The transaction function gets a new snapshot of the document, and then updates the value of votes by one. Other users are blocked from accessing this document while the transaction is active. If a user is blocked because of a transaction, Cloud Firestore will attempt to rerun their function.

  1. Run the app and make sure you can vote by tapping on a baby name list item. If you have two devices (either real or emulated), see if you can run the app on both devices. When you vote on one of the devices, the vote will increase on both devices.

    You can also go back to the Firebase console and add new names to the Cloud Firestore database and see them appear instantly in your app.

A common mistake is updating a database as follows:

onTap: () {           // DO NOT USE THIS VERSION!
       document.reference.updateData({'votes': document['votes'] + 1});
},

Like the transaction, this code reads the value of votes, adds one, and then updates votes to the new value. When you run it, your app appears to work just fine.

However, this code creates a race condition. If two people with your app vote at the same time the value of votes would increase by only one, even though two people voted for the name. This is because both apps would read the current value at the same time, add one, and then write the same value back to the database. Neither user would notice anything wrong, because they would both see the value of votes increase. It's extremely difficult to detect this problem through testing because triggering the error depends on doing two things inside a very small time window.

The value of votes is a shared resource, and any time you update a shared resource, especially when the new value depends on the old value, there is a risk of creating a race condition. Instead, when updating a value in any database, you should use a transaction.

Congratulations!

You now know the basics of integrating Flutter apps with Firebase.

What we've covered

Ready to share your new app with friends? Check out how you can get the platform-specific binaries for your Flutter app (an IPA file for iOS and an APK file for Android).

Additional resources

For more information on Firebase, see:

You may also find these developer resources useful as you continue working with the Flutter framework:

Here are some ways to get the latest news about Flutter:

Tell us how we're doing

How likely are you to recommend trying Flutter to your friends and colleagues? (from 1="Very unlikely" to 5="Very likely")

1 2 3 4 5