Implementing Firebase Firestore for Cloud Storage in Flutter

Flutter has revolutionized mobile app development by providing a fast and efficient way to build beautiful, natively compiled applications for multiple platforms from a single codebase. Firebase, Google’s mobile development platform, complements Flutter by offering various tools and services, including Firestore, a NoSQL document database. Using Firestore for cloud storage in Flutter applications provides a scalable and reliable solution for managing and syncing data across devices.

What is Firebase Firestore?

Firestore is a flexible, scalable NoSQL cloud database for mobile, web, and server development from Firebase and Google Cloud. It keeps your data in sync across client apps through realtime listeners and offers offline support so you can build responsive apps that work regardless of network latency or internet connectivity.

Why Use Firestore in Flutter?

  • Real-time Data Synchronization: Automatically syncs data across all connected devices in real time.
  • Offline Support: Allows users to access and modify data even without an internet connection, with changes synchronized once the connection is restored.
  • Scalability: Scales seamlessly to handle large amounts of data and concurrent users.
  • Security: Provides a robust security model to protect your data.
  • Ease of Use: Simplifies database management and data retrieval with an intuitive API.

How to Implement Firebase Firestore in Flutter

To implement Firebase Firestore in a Flutter app, follow these steps:

Step 1: Set up a Firebase Project

  1. Go to the Firebase Console and create a new project.
  2. Follow the prompts to set up your project, and register your Flutter app within the project settings.

Step 2: Add Firebase to Your Flutter App

Install Firebase CLI

If you haven’t already, install the Firebase CLI. You’ll need Node.js and npm installed.

npm install -g firebase-tools
Login to Firebase
firebase login
Initialize Firebase in Your Flutter Project

From the root of your Flutter project, run:

flutterfire configure

This command will guide you through the process of linking your Flutter app to your Firebase project. You’ll be asked to select your project and the platforms (Android, iOS, Web, etc.) that you are targeting. Follow the instructions in the terminal to complete the configuration. This step generates the firebase_options.dart file in your lib folder.

Step 3: Add Firebase Core and Firestore Dependencies

Add the necessary dependencies to your pubspec.yaml file:

dependencies:
  firebase_core: ^2.15.0 # Use the latest version
  cloud_firestore: ^4.9.0 # Use the latest version

Run flutter pub get to install these dependencies.

Step 4: Initialize Firebase

In your main.dart file, initialize Firebase before running your app:

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart'; // Import the generated file

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firestore Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firestore Demo'),
      ),
      body: Center(
        child: Text('Firestore Example'),
      ),
    );
  }
}

Step 5: CRUD Operations with Firestore

Now, let’s implement the basic CRUD (Create, Read, Update, Delete) operations with Firestore.

Create (Add Data)

To add data to Firestore, use the add or set method. The add method automatically generates a unique ID for the document. The set method allows you to specify the document ID.

import 'package:cloud_firestore/cloud_firestore.dart';

// Add data
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"));
}


//Set Data with a specific ID
Future setData() async {
   CollectionReference users = FirebaseFirestore.instance.collection('users');

   return users.doc('some-user-id').set({  //Use .doc() with specific id, .add for auto-generated
     'full_name': 'Jane Smith',
     'age': 25,
     'email': 'jane.smith@example.com'
   })
   .then((value) => print("User Added"))
   .catchError((error) => print("Failed to add user: $error"));
}

Usage:

ElevatedButton(
    onPressed: () {
        addData();
    },
    child: Text('Add User')
),
ElevatedButton(
   onPressed: () {
        setData();
   },
   child: Text('Set User with ID')
),
Read (Get Data)

To read data from Firestore, use the get method on a document or a collection.

// Get data
Future getData() async {
    DocumentReference user = FirebaseFirestore.instance.collection('users').doc('some-user-id');

    user.get().then((DocumentSnapshot documentSnapshot) {
      if (documentSnapshot.exists) {
        print('Document data: ${documentSnapshot.data()}');
      } else {
        print('Document does not exist on the database');
      }
    });
}

Reading data from a Collection:

// Get data from collection
Future getCollectionData() async {
    CollectionReference users = FirebaseFirestore.instance.collection('users');

    users.get().then((QuerySnapshot querySnapshot) {
      querySnapshot.docs.forEach((doc) {
        print(doc.data());
      });
    });
}

Realtime Data using StreamBuilder:

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

class RealTimeData extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Real-Time Data')),
      body: 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 CircularProgressIndicator();
          }

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

To update data in Firestore, use the update method:

// Update data
Future updateData() async {
  DocumentReference user = FirebaseFirestore.instance.collection('users').doc('some-user-id');

  return user.update({
    'age': 31,
    'email': 'john.doe.updated@example.com'
  })
  .then((value) => print("User Updated"))
  .catchError((error) => print("Failed to update user: $error"));
}
Delete Data

To delete data from Firestore, use the delete method:

// Delete data
Future deleteData() async {
  DocumentReference user = FirebaseFirestore.instance.collection('users').doc('some-user-id');

  return user.delete()
  .then((value) => print("User Deleted"))
  .catchError((error) => print("Failed to delete user: $error"));
}

Using Firestore Security Rules

Firestore Security Rules define who has access to your data and what they are allowed to do. You can configure these rules in the Firebase Console under the Firestore Database section.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

This basic example allows each user to read, update, and delete their own data, and allows authenticated users to create new user documents.

Conclusion

Firebase Firestore provides a powerful and scalable solution for cloud storage in Flutter applications. By integrating Firestore, you can easily manage and synchronize data in real time, support offline capabilities, and secure your data with robust security rules. Following the steps outlined in this guide, you can effectively implement Firestore in your Flutter apps and create responsive, data-driven experiences.