Flutter Connecting to Firestore

Flutter Connecting to Firestore

[[../../../_notes/_tech/Flutter/Flutter]]

First add the dependency

open the file pubspec.yaml and add the dependency for cloud_firestore

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^0.8.2

Make sure to run packages get to pull the new dependencies

Connecting Android

Once you have your firebase project and firestore database created, you’ll need to add the keys for both Android and iOS. This section will just cover android

Create a new app in firebase and choose the android type.

  • Get the Android package name from android/app/src/main/AndroidManifest.xml -> manifest/package

  • Download the config file google-services.json and move it to the android/app directory

  • 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
       }
    }
    

Using Firestore from your app

Replace the contents of 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 Firestorereturn _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>";
}d

Now replace the _buildBody method with the following

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);
   },
 );
}

Change the _buildList signature to this:

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

Change the _buildListItem signature to this:

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

Update Firestore from your app

Find the line that says onTap: () => print(record) and change it to this:

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

Firestore Snapshots

We just used the stream builder to access the records

Here are the types returned by the various methods

QuerySnapshot            <- Firestore.instance.collection('baby').snapshots()
DocumentSnapshot         <- Firestore.instance.collection('baby').snapshots().data.documents
DocumentSnapshot         <- Firestore.instance.collection('baby').snapshots().data.documents.map( data )
//   ^^ the `.map( (data) => function(context, data) )` sends each document into the `function` and provides all of the individual `function` returns

DocumentReference        <- Firestore.instance.collection('baby').snapshots().data.documents.map( data.reference )
Map<String, dynamic>     <- Firestore.instance.collection('baby').snapshots().data.documents.map( data.data )
List (ListView)          <- Firestore.instance.collection('baby').snapshots().data.documents.map().toList()
StreamBuilder<QuerySnapshot>(
      stream: Firestore.instance.collection('baby').snapshots()
      _buildList(context, snapshot.data.documents);
...

Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
...
      children: snapshot.map( (data) => _buildListItem(context, data) ).toList(),
...
_buildListItem(BuildContext context, DocumentSnapshot data) {
...
    final record = Record.fromSnapshot(data);

    onTap: () => record.reference.updateData({'votes': record.votes + 1})
...
class Record {
  final DocumentReference reference;
  ...
  Record.fromSnapshot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);
  Record.fromMap(Map<String, dynamic> map, {this.reference})
        name = map['name'],
        votes = map['votes'];