My Flutter Adventure — Flutter On Fire!

Ümit Duran
6 min readNov 5, 2018

In this article, I will show you how to build a Flutter app in with Firebase. Also, I will complete the Firebase & Flutter Codelab.

In my previous post, we looked Widget Structure. You can read from here.

Flutter and Firebase work hand-in-hand to help you build mobile apps in record time. Firebase gives you access to backend services for mobile applications — including authentication, storage, database, and hosting — without maintaining your own servers. For more details and the latest new’s about Firebase;

Create a Flutter Project

This guide explains how can we create a new Flutter project.

App name will be thebaby_names instead of myapp. Then, open the pubspec.yaml and add a dependency for cloud_firestore, then save the file.

dependencies:
flutter:
sdk: flutter
cloud_firestore: ^0.8.2 # new

Add the user interface

Then replace the all code in main.dart with the following:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

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

final dummySnapshot = [
{"name": "Filip", "votes": 15},
{"name": "Abraham", "votes": 14},
{"name": "Richard", "votes": 11},
{"name": "Ike", "votes": 10},
{"name": "Justin", "votes": 1},
];

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Baby Names',
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() {
return _MyHomePageState();
}
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Baby Name Votes')),
body: _buildBody(context),
);
}

Widget _buildBody(BuildContext context) {
// TODO: get actual snapshot from Cloud Firestore
return _buildList(context, dummySnapshot);
}

Widget _buildList(BuildContext context, List<Map> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}

Widget _buildListItem(BuildContext context, Map data) {
final record = Record.fromMap(data);

return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Text(record.votes.toString()),
onTap: () => print(record),
),
),
);
}
}

class Record {
final String name;
final int votes;
final DocumentReference reference;

Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
assert(map['votes'] != null),
name = map['name'],
votes = map['votes'];

Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);

@override
String toString() => "Record<$name:$votes>";
}

Create a Firebase project

In the Firebase console, click Add project. Then Create project.

Configure iOS

In the Firebase console, select Project Overview in the left nav, then click the iOS button under “Get started by adding Firebase to your app”.

Run the command open ios/Runner.xcworkspace to open Xcode. And copy the Bundle Identifier value under the Runner in the left pane as shown in the screencap below.

Copy Bundle Identifier

Then, paste copied Bundle Identifier into the iOS bundle ID field

Continuing in Firebase, follow the instructions to download the config file GoogleService-Info.plist. Then drag the GoogleService-Info.plist file (that you just downloaded) into that Runner subfolder. Then click next and skip the other steps.

Configure Android

Click Add App and Android. In your Flutter app directory, open the file android/app/src/main/AndroidManifest.xml and copy package name value.

Click Register App, download the google-services.json file. Go to your Flutter app directory, then move the google-services.json file (that you just downloaded) into the android/app directory.

In your IDE or editor, open android/app/build.gradle, then add the following line as the last line in the file:

apply plugin: 'com.google.gms.google-services'

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

buildscript {
repositories {
// ...
}

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

You configured the iOS and Android. Now we can continue with the next steps.

Create your Cloud Firestore Database

Open the Firebase console, then select the Firebase project that you created during setup. From the left nav Develop section, select Database.
In the Cloud Firestore pane, click Create database.
In the Security rules for Cloud Firestore dialog, select Start in test mode, then click Enable.

Click Add Collection, set the collection’s name to baby, then click Next.

You can now add documents to your collection. Each document has a Document ID, and we’ll need to have name and votes fields.

Add additional baby names by clicking Add Document.

Connect your Flutter App to Cloud Firestore

It’s time to fetch our collection (baby) and use it instead of our dummySnapshot object.

From Dart, you get the reference to Cloud Firestore by calling Firestore.instance. Specifically for our collection of baby names, call Firestore.instance.collection('baby').snapshots() to return a stream of snapshots.

Let’s plug that stream of data into our Flutter UI using a StreamBuilder widget.

In your IDE or editor, open lib/main.dart, then find and replace the_buildBody method.

Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('baby').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();

return _buildList(context, snapshot.data.documents);
},
);
}

Then change _buildList method;

Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
...

And _buildLisItem methods signature like this;

Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);

Add Interactivity

In lib/main.dart, find the line that says onTap: () => print(record). Change it to this:

onTap: () => record.reference.updateData({'votes': record.votes + 1})

When the user taps the tile containing a name, you are telling Cloud Firestore to update the data of that reference. In turn, this causes Cloud Firestore to notify all listeners with the updated snapshot. As your app is listening through the StreamBuilder implemented above, it's updated with the new data.

Use a Cloud Firestore transaction

In lib/main.dart, find the line that says onTap: () => record.reference.updateData({'votes': record.votes + 1}). Replace it with this:

onTap: () => Firestore.instance.runTransaction((transaction) async {
final freshSnapshot = await transaction.get(record.reference);
final fresh = Record.fromSnapshot(freshSnapshot);

await transaction
.update(record.reference, {'votes': fresh.votes + 1});
}),

The voting interaction now takes a bit more time to complete. The upside, though, is that each vote counts — you removed the race condition.

How does this work? By wrapping the read and write operations in one transaction, you’re telling Cloud Firestore to only commit a change if there was no external change to the underlying data while the transaction was running. If two users aren’t concurrently voting on that particular name, the transaction runs exactly once. But if the number of votes changes between the transaction.get(...) and the transaction.update(...) calls, the current run isn't committed, and the transaction is retried. After 5 failed retries, the transaction fails.

That’s all! This is the basics of integrating Firebase Cloud Firestore to Flutter application.

Thanks for reading!

Leave a comment below or tweet me if with any questions/suggestions!

Also, I have GitHub repository for my Flutter series. I will share codes, links, and posts there.

--

--