Working with Firestore, Firebase’s Scalable NoSQL Cloud Database, for Storing and Retrieving Application Data in Flutter

Firebase is a comprehensive platform offering various services for building mobile and web applications. Among these, Firestore stands out as a flexible and scalable NoSQL cloud database, especially useful for Flutter applications. Firestore enables developers to efficiently store and retrieve data, handle real-time updates, and manage complex data structures without the hassle of managing server infrastructure.

What is Firestore?

Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of use. As part of the Firebase suite, Firestore allows developers to store and synchronize data across multiple client platforms—iOS, Android, and web. Unlike traditional SQL databases, Firestore stores data in documents, which are organized into collections. This structure facilitates easy management of complex data and efficient querying.

Why Use Firestore with Flutter?

  • Real-time Data Updates: Firestore supports real-time listeners, enabling automatic data synchronization across all connected clients.
  • Scalability: Built to handle large amounts of data and high traffic without performance degradation.
  • Offline Support: Firestore caches data locally, allowing apps to function even when offline and automatically synchronizing changes once online.
  • Flexible Data Model: NoSQL structure makes it easy to store and manage complex and evolving data.
  • Seamless Integration: Integrates smoothly with other Firebase services such as Authentication and Cloud Functions.

Setting Up Firestore in Your Flutter Project

Before you start using Firestore, you need to set up Firebase in your Flutter project.

Step 1: Create a Firebase Project

If you don’t already have one, create a new Firebase project in the Firebase Console (https://console.firebase.google.com/).

Step 2: Add Firebase to Your Flutter App

  • Register Your App: In the Firebase Console, add a new Flutter app by providing your app’s package name (Android) or bundle ID (iOS).
  • Download google-services.json (Android) or GoogleService-Info.plist (iOS): Download the configuration file for your respective platform and add it to your Flutter project.
    • For Android, place the google-services.json file in the android/app/ directory.
    • For iOS, add the GoogleService-Info.plist file to the root of your Xcode project using Xcode.
  • Configure Gradle (Android): In your project-level build.gradle file (android/build.gradle), add the Google Services plugin:

buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.10' // Check for the latest version
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

Then, in your app-level build.gradle file (android/app/build.gradle), apply the plugin at the top:


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

dependencies {
    implementation platform('com.google.firebase:firebase-bom:30.0.0') // Use the latest Firebase BoM
    implementation 'com.google.firebase:firebase-analytics'
}

Step 3: Add Firebase Core and Firestore Dependencies

In your Flutter project’s pubspec.yaml file, add the firebase_core and cloud_firestore dependencies:


dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.0.0
  cloud_firestore: ^4.0.0

Run flutter pub get to install the dependencies.

Step 4: Initialize Firebase

In your Flutter app, initialize Firebase before using any other Firebase services. Typically, you do this in your main.dart file:


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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Firestore Example'),
        ),
        body: Center(
          child: Text('Working with Firestore in Flutter'),
        ),
      ),
    );
  }
}

Storing Data in Firestore

To store data in Firestore, you need to reference a specific collection and document.

Adding Data to a Collection

Here’s how you can add data to a Firestore collection:


import 'package:cloud_firestore/cloud_firestore.dart';

Future addData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');
  
  return users
      .add({
        'full_name': 'John Doe',
        'age': 30,
        'email': 'john.doe@example.com'
      })
      .then((value) => print("User Added"))
      .catchError((error) => print("Failed to add user: $error"));
}

Setting Data in a Document

If you want to set data in a specific document (either creating it or overwriting existing data), use the set method:


Future setData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');
  
  return users
      .doc('john_doe') // Set the document ID
      .set({
        'full_name': 'John Doe',
        'age': 30,
        'email': 'john.doe@example.com'
      })
      .then((value) => print("User Added"))
      .catchError((error) => print("Failed to add user: $error"));
}

Updating Data in a Document

To update specific fields in an existing document, use the update method:


Future updateData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');
  
  return users
      .doc('john_doe')
      .update({
        'age': 31
      })
      .then((value) => print("User Updated"))
      .catchError((error) => print("Failed to update user: $error"));
}

Deleting Data from a Document

To delete a document from a collection, use the delete method:


Future deleteData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');
  
  return users
      .doc('john_doe')
      .delete()
      .then((value) => print("User Deleted"))
      .catchError((error) => print("Failed to delete user: $error"));
}

Retrieving Data from Firestore

Firestore provides several ways to retrieve data, depending on your needs.

Getting a Single Document

To retrieve a single document, use the get method:


Future getData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');
  
  DocumentSnapshot snapshot = await users.doc('john_doe').get();
  
  if (snapshot.exists) {
    print('Document data: ${snapshot.data()}');
  } else {
    print('Document does not exist');
  }
}

Getting All Documents in a Collection

To retrieve all documents in a collection, use the get method on the collection:


Future getAllData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');
  
  QuerySnapshot querySnapshot = await users.get();
  
  querySnapshot.docs.forEach((doc) {
    print('Document data: ${doc.data()}');
  });
}

Real-time Data Updates with StreamBuilder

For real-time updates, use the StreamBuilder widget along with Firestore’s snapshots method:


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

class RealTimeData extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: FirebaseFirestore.instance.collection('users').snapshots(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.hasError) {
          return Text('Something went wrong');
        }

        if (snapshot.connectionState == ConnectionState.waiting) {
          return Text("Loading");
        }

        return ListView(
          children: snapshot.data!.docs.map((DocumentSnapshot document) {
            Map data = document.data() as Map;
            return ListTile(
              title: Text(data['full_name']),
              subtitle: Text(data['email']),
            );
          }).toList(),
        );
      },
    );
  }
}

Querying Data in Firestore

Firestore supports complex queries to filter and order data.

Filtering Data with where


Future filterData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');

  QuerySnapshot querySnapshot = await users
      .where('age', isGreaterThan: 25)
      .get();

  querySnapshot.docs.forEach((doc) {
    print('Document data: ${doc.data()}');
  });
}

Ordering Data with orderBy


Future orderData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');

  QuerySnapshot querySnapshot = await users
      .orderBy('age')
      .get();

  querySnapshot.docs.forEach((doc) {
    print('Document data: ${doc.data()}');
  });
}

Limiting Data with limit


Future limitData() async {
  CollectionReference users = FirebaseFirestore.instance.collection('users');

  QuerySnapshot querySnapshot = await users
      .limit(5)
      .get();

  querySnapshot.docs.forEach((doc) {
    print('Document data: ${doc.data()}');
  });
}

Conclusion

Firestore is a powerful and flexible NoSQL cloud database ideal for Flutter applications requiring scalable and real-time data management. By integrating Firestore, you can efficiently store and retrieve data, manage real-time updates, and build robust, data-driven applications without the complexity of server-side management. From setting up your Flutter project to implementing real-time listeners, this guide provides a solid foundation for leveraging Firestore’s capabilities in your Flutter apps.