image

03 Dec 2025

9K

35K

Flutter & Firebase Firestore: Mastering CRUD Operations

Building dynamic, data-driven mobile applications often requires a robust backend and a powerful frontend framework. Flutter, Google's UI toolkit for crafting natively compiled applications for mobile, web, and desktop from a single codebase, pairs exceptionally well with Firebase, Google's comprehensive platform for developing mobile and web applications. Among Firebase's many services, Firestore stands out as a flexible, scalable, serverless NoSQL document database that excels at real-time data synchronization.

This article will guide you through the process of integrating Firebase Firestore into a Flutter application and performing fundamental CRUD (Create, Read, Update, Delete) operations, empowering you to build interactive and responsive user experiences.

Prerequisites

  • Flutter SDK installed and configured.
  • A Google account to set up a Firebase project.
  • Basic understanding of Flutter widgets and Dart programming.

1. Setting Up Firebase in Your Flutter Project

1.1 Create a Firebase Project

Navigate to the Firebase Console and create a new project. Follow the on-screen instructions.

1.2 Register Your App with Firebase

Within your Firebase project, add an Android and/or iOS app. Follow these steps:

  • Provide your Android package name (e.g., com.example.your_app) and optionally an App nickname and SHA-1 signing certificate.
  • Download the google-services.json file and place it in your Flutter project's android/app/ directory.
  • For iOS, provide your iOS bundle ID (e.g., com.example.yourApp).
  • Download the GoogleService-Info.plist file and place it in your Flutter project's ios/Runner/ directory using Xcode.

1.3 Add Firebase Dependencies

Open your pubspec.yaml file and add the following dependencies under dependencies::


dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.25.3 # Use the latest version
  cloud_firestore: ^4.15.3 # Use the latest version

Run flutter pub get in your terminal to fetch the new packages.

1.4 Initialize Firebase

To use Firebase services, you need to initialize them. Update your main.dart file:


import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; // This file will be generated

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firestore CRUD',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Firestore CRUD'),
      ),
      body: const Center(
        child: Text('Your CRUD app will go here!'),
      ),
    );
  }
}

You'll need to generate firebase_options.dart. You can do this by installing the Firebase CLI and running flutterfire configure in your project root. For more details, refer to the official FlutterFire documentation.

2. Firestore Data Model (Example)

For this tutorial, let's imagine we're managing a list of items. Each item will have a name (String) and a quantity (int).

  • Collection: items
  • Document: Unique ID (auto-generated by Firestore)
  • Fields:
    • name: String
    • quantity: int

Before proceeding, enable Firestore in your Firebase project from the Firebase Console. Go to "Firestore Database" and click "Create database". Start in test mode for simplicity, then set up appropriate security rules for production.

3. Implementing CRUD Operations

Now, let's create a simple Flutter UI to interact with Firestore.


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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firestore CRUD',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _quantityController = TextEditingController();

  final CollectionReference _items =
      FirebaseFirestore.instance.collection('items');

  // Function to show the add/edit dialog
  Future<void> _showUpsertDialog([DocumentSnapshot? documentSnapshot]) async {
    if (documentSnapshot != null) {
      _nameController.text = documentSnapshot['name'];
      _quantityController.text = documentSnapshot['quantity'].toString();
    } else {
      _nameController.text = '';
      _quantityController.text = '';
    }

    await showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      builder: (BuildContext context) {
        return Padding(
          padding: EdgeInsets.only(
            top: 20,
            left: 20,
            right: 20,
            bottom: MediaQuery.of(context).viewInsets.bottom + 20,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              TextField(
                controller: _nameController,
                decoration: const InputDecoration(labelText: 'Item Name'),
              ),
              TextField(
                keyboardType: const TextInputType.numberWithOptions(decimal: true),
                controller: _quantityController,
                decoration: const InputDecoration(labelText: 'Quantity'),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                child: Text(documentSnapshot == null ? 'Create' : 'Update'),
                onPressed: () async {
                  final String name = _nameController.text;
                  final int? quantity = int.tryParse(_quantityController.text);

                  if (name.isNotEmpty && quantity != null) {
                    if (documentSnapshot == null) {
                      // CREATE operation
                      await _items.add({"name": name, "quantity": quantity});
                    } else {
                      // UPDATE operation
                      await _items
                          .doc(documentSnapshot.id)
                          .update({"name": name, "quantity": quantity});
                    }

                    _nameController.text = '';
                    _quantityController.text = '';
                    Navigator.of(context).pop();
                  }
                },
              )
            ],
          ),
        );
      },
    );
  }

  // DELETE operation
  Future<void> _deleteItem(String productId) async {
    await _items.doc(productId).delete();

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('You have successfully deleted an item')),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Firestore CRUD'),
      ),
      // READ operation - StreamBuilder for real-time updates
      body: StreamBuilder(
        stream: _items.snapshots(), // listening for changes
        builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
          if (streamSnapshot.hasData) {
            return ListView.builder(
              itemCount: streamSnapshot.data!.docs.length,
              itemBuilder: (context, index) {
                final DocumentSnapshot documentSnapshot =
                    streamSnapshot.data!.docs[index];
                return Card(
                  margin: const EdgeInsets.all(10),
                  child: ListTile(
                    title: Text(documentSnapshot['name']),
                    subtitle: Text(documentSnapshot['quantity'].toString()),
                    trailing: SizedBox(
                      width: 100,
                      child: Row(
                        children: [
                          // UPDATE button
                          IconButton(
                            icon: const Icon(Icons.edit),
                            onPressed: () => _showUpsertDialog(documentSnapshot),
                          ),
                          // DELETE button
                          IconButton(
                            icon: const Icon(Icons.delete),
                            onPressed: () => _deleteItem(documentSnapshot.id),
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
            );
          }
          return const Center(
            child: CircularProgressIndicator(),
          );
        },
      ),
      // Floating Action Button for CREATE operation
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showUpsertDialog(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Explanation of CRUD Operations:

Create (Add Data)

A new document is added to the items collection using the add() method. Firestore automatically generates a unique ID for each new document.


await _items.add({"name": name, "quantity": quantity});

In our example, this is triggered when the "Create" button is pressed in the _showUpsertDialog and documentSnapshot is null.

Read (Retrieve Data)

Firestore offers two main ways to read data:

  1. Get a document once: Use collectionRef.get() for a single fetch.
  2. Listen for real-time updates: Use collectionRef.snapshots() to receive a Stream of QuerySnapshot objects. Any change to the collection will automatically update the UI.

Our example uses StreamBuilder with _items.snapshots(). This allows the UI to automatically rebuild whenever data in the 'items' collection changes, providing a real-time experience.


StreamBuilder(
  stream: _items.snapshots(), // listens for changes
  builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
    if (streamSnapshot.hasData) {
      // Build ListView from streamSnapshot.data!.docs
    }
    return const Center(child: CircularProgressIndicator());
  },
)

Update (Modify Data)

To update an existing document, you need its unique ID. The doc(id).update() method then allows you to modify specific fields. If a field does not exist, it will be created.


await _items
    .doc(documentSnapshot.id)
    .update({"name": name, "quantity": quantity});

In our example, this is triggered when the "Update" button is pressed in the _showUpsertDialog and documentSnapshot is not null.

Delete (Remove Data)

Similar to updating, deleting a document requires its ID. The doc(id).delete() method removes the entire document from the collection.


await _items.doc(productId).delete();

Our example triggers this when the delete icon button is pressed for a specific list item.

Conclusion

You have successfully learned how to integrate Firebase Firestore into your Flutter application and perform the essential CRUD operations. By leveraging Firestore's real-time capabilities and Flutter's reactive UI framework, you can build dynamic and engaging applications with minimal backend code.

This foundation opens the door to more advanced features such as querying, filtering, pagination, and robust security rules, allowing you to create even more powerful and scalable applications.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is