Flutter Firebase CRUD App [Create, Read, Update, Delete]

flutter firebase crud app create read update delete data

In this Flutter tutorial, we’ll learn how to properly create a Flutter Firebase CRUD android app. We’ll first learn how to set up Firebase and Firestore and connect them to our Flutter app. Then we’ll perform 4 operations. These are creating, updating, reading, and deleting data.

We’ll also be providing the Github repo link that contains the complete source code of this project.

So let’s get right into creating a Flutter CRUD app with Firebase.

Introduction: Flutter Firebase CRUD App

It specifies a Flutter app that will be used to create, read, update and delete data. We’ll be creating an android app in this tutorial. We’ll be using Firebase and Firestore to manage data. The concepts that we’ll learn in this tutorial are listed below.

  • Setup Firebase project and connect it to our Flutter app
  • Setup Firestore database that will be used to manage data
  • Send, update, fetch and delete data from Firebase Firestore

Let’s now jump right into its practical step-by-step implementation.

Implementing Flutter Firebase CRUD Operations App (Step By Step Guide)

Below steps will guide us through how to properly create a Flutter Firebase CRUD app.

Step 1: Setup Firebase

Let’s now see how to set up Firebase in order to perform Flutter Firebase CRUD operations.

1. Create a Project

Click here to go to Firebase, navigate to the console, and create a new project.

2. For Android

Then click on the android icon to set up firebase for android.

firebase project setup for flutter app

It’ll ask for the android package name so we’ve to create a Flutter project as well. After creating the project, go to the app build.gradle file. The path is android/app/build.gradle.

applicationId "com.example.firebase_crud_app"
 We’ve to copy the application id. In your case, the name may be different because it depends on the name of the project. Just copy the text inside quotes and paste it into the android package name section and click on register. See below:

gradle file showing application id

flutter firebase android package name

2.1. Download the JSON File

Download the services JSON file. It contains the information to link your firebase project to your Flutter project.

firebase json file

After downloading the file, just paste it into the android app folder. Also, make sure that the name is the same as shown in the below picture. Avoid using the names like google-services(1).json etc.

flutter app google services json file

2.2. Configurations

Follow the below steps in order to properly understand the configurations for Flutter Firebase CRUD app.

Step 1: Google Services Classpath

Click on the next button, copy the google services classpath and paste it inside the dependencies section of the android build.gradle file. The location is android/build.gradle. See below:

classpath 'com.google.gms:google-services:4.3.15'

flutter firebase google services class path

flutter gradle google services apply plugin

Step 2: Apply Plugin

Open the android app build.gradle file and paste the below line as shown in the image. The location is android/app/build.gradle.

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

flutter gradle file apply plugin google services

Also, set the min SDK version to 21 as shown in the below image. It’s in the same android app build.gradle file.

minSdkVersion 21

flutter min sdk version 21

Now you can click on the next and continue to the console in order to finish the setup.

Click here if you want to learn the setup for IOS.

Step 2: Import Cloud Firestore Package

cloud_firestore: ^4.3.1

In order to use Firebase in our project to perform Flutter Firebase CRUD operations, we’ve used the cloud Firestore database by importing the above package in our pubspec.yaml file and run Flutter pub get. Click here to get the latest version of cloud firestore.

flutter pubspec.yaml file cloud firestore package import

Step 3: Create Firestore Database

For that, we’ve to click on create a database as shown in the below image.

flutter firebase cloud firestore database creation

Then we’ve to select the text mode and select next. You can choose the location but we prefer to go with the default one.

flutter firebase cloud firestore database test mode

After that, click the button and your database is now created.

In this database, we can have multiple collections. Each collection can store multiple documents with its unique keys. We can store our data using key-value pairs in these documents. See the below image.

flutter firebase cloud firestore database collection documents fields

We’ll see how to create these fields/ add data from our Flutter app. In the meantime, let’s see how our data will be stored. See the below image.

flutter firebase cloud firestore database collection documents fields

We can see that we can easily create new collections inside documents as well. Inside these documents, we can again create collections. This is how its hierarchy works. In this tutorial, we’ll just make things simple so it’s easy to understand.

Step 4: Firebase Setup in main() Method

In order to use Firebase in the Flutter project, we’ve to initialize the Firebase inside the main() method. We also have to import the Firebase core library. See below:

import 'package:firebase_core/firebase_core.dart';
Future main() async{
    WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}

If the app is running then stop it, clean the project by running the flutter clean command in the project’s terminal and run it again.

If the app loads successfully then it means that the Firebase is configured properly. If not, then it means you’ve missed something. Do check the above steps again in order to avoid any misconfigurations.

Step 5: Create UI

Let’s now create a user interface to send, fetch, update and delete data which simply means performing Flutter Firebase CRUD operations.

For that, we’ll create 3 screens. These are:

Display Data – The first screen will fetch and display the data if it’s available.

Send Data – This screen will have 3 Flutter text fields. These will be used to get the name, age, and email of the user. Also, a Flutter material button will be used that’ll trigger the validation and manage data.

Update Data – This screen will update the currently available data. The interface will have text fields and material buttons which are specified in the send data screen.

Delete Data – It’ll be used to delete/remove the data from the firestore database. It doesn’t need a dedicated screen. We’ll delete that data from a simple button which will be specified in the display data screen.

Let’s now create the UI for these screens. The whole source code will be provided in the end. In these sections, we’ll look at the logical part and functions that are used to manage data.

Send Data Screen

flutter floating action button widget and flutter appbar

The Flutter floating action button at the bottom right position is used to navigate to the add data screen. The below image shows this screen.

flutter textfield widget and a flutter material button widget

Flutter Text Editing Controllers

We’ve used text editing controllers to fetch and store data from users. See below:

  TextEditingController nameController = TextEditingController();
  TextEditingController ageController = TextEditingController();
  TextEditingController emailController = TextEditingController();
Don’t forget to dispose the controllers. See below:
 @override
 void dispose() {
    nameController.dispose();
    ageController.dispose();
    emailController.dispose();
    super.dispose();
 }
We’ve used these controllers like this in the text field. See below:
TextField(
          controller: nameController
            //
         )
Flutter Material Button
We’ve specified the logic to send data to Firebase Firestore. It’ll first check whether all the text fields are empty or not. If at least one is empty, then it’ll show a Flutter snack bar widget. If not then it’ll send data to Firebase Firestore. See the below code:
 onPressed: () async {
                setState(() {});
                if (nameController.text.isEmpty &&
                    ageController.text.isEmpty &&
                    emailController.text.isEmpty) {
                  ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('Fill in all fields')));
                } else {

                  //reference to document
                  final dUser = FirebaseFirestore.instance
                      .collection('users')
                      .doc('CustomDocumentName1');

                  final jsonData = {
                    'name': nameController.text,
                    'age': int.parse(ageController.text),
                    'email': emailController.text
                  };

                  showProgressIndicator = true;
                  //create document and write data to firebase
                  await dUser.set(jsonData).then((value) {
                    nameController.text = '';
                    ageController.text = '';
                    emailController.text = '';
                    showProgressIndicator = false;
                    setState(() {});
                  });
                }
              },

cloud firestore database collection documents fields key value pairs

It’ll first create a collection of id(users) if it’s not already present.

Then if we’ve specified a string inside docs(‘docName’) then this will be used. If we pass it no argument like this doc() then it’ll create a document using a unique id.

We’ve created a map(key, value) pair that’ll store the name, age, and email as values to their specified keys.

We now have a reference to our document. We’ll call the set method and pass this JSON to it. We’ve made the function async so that it’ll wait for the method to send data to Firebase Firestore as it takes some time.

After that, all the fields will be empty again to send more data.

We also have used a Flutter circular progress indicator widget that will be shown during the sending of data. If we try to add the same data to the same document of the same collection, then it’ll just replace the existing data. See below:

final jsonData = {
             'name': nameController.text,           
                  };

Try it and you’ll see that the whole document will be replaced with new data(key, value pair). That means if we want to update a single item inside the document then we’ll have to specify each key-value pair. If not, then the new data will only consist of the specified key-value pair.

In order to solve this, we can use the update method. See the section (update data screen) for a detailed guide on the update method.

Fetch Data Screen

We’ll fetch and show data from firebase. For that, we’ve created some documents so we can display their data. We’ve used stream builder so it gets updated whenever a change occurs in the documents. See below:

 stream: FirebaseFirestore.instance.collection('users').snapshots()
The snapshot method is used to get all the documents of the specified collection. We’ve specified a condition that checks if we have any data(documents) or not and during this process, just show a circular progress indicator.
streamSnapshot.hasData ? show list of data : show circular progress indicator
Flutter List View Builder
We’ve used a Flutter list view builder to show the list of data. We’ll show the name, age, and email that is stored in each document. Also, two Flutter clickable icons to edit and delete the document.
itemCount: streamSnapshot.data!.docs.length
This is how we can fetch the number/length of items(documents).
Now in order to fetch data using a key, we’ve to use the below code:
streamSnapshot.data!.docs[index]['name']
It’ll take the value specified to key(name) from each document. We’ve used indexing so it can iterate over all the documents of the collection.

flutter list view widget showing flutter text widget and flutter icons

For demonstration, we’ve added some data and then we’ve navigated to the home screen to see if it has fetched and displayed a list of that data or not. The above image shows that the data has been successfully fetched from the database.

Edit Data Screen

We’ll navigate to this screen using the edit icon that is shown on the right side of each list item. We’ve modified the code to add/send data to the database. We’ve added a new key-value pair that will be used to store the unique document id. Using this id, we’ll specify the document to edit. See below:

Step 1
 final dUser = FirebaseFirestore.instance
                      .collection('users')
                      .doc(widget.id.isNotEmpty ? widget.id : null);
We’ve specified a condition that if the user navigates to the screen(the same screen is used to update and add new data) using the edit icon then send existing data as well which means an id(unique collection ID) will also be provided.
We’ve specified a condition that will check if the id is empty, which means the user hasn’t navigated to this screen using the edit icon so pass null to it, else pass the specified id. Passing null to it means a new collection will be created with a new unique id when it’s used.
Step 2
if (widget.id.isNotEmpty) {
       docID = widget.id;
                      } 
else {
       docID = dUser.id;
     }
It’ll check if the ID does not have an empty value which means the user is updating the existing data, so used the existing ID, else use the newly created document ID.
Step 3
  final jsonData = {     
            'name': nameController.text,
            'age': int.parse(ageController.text),
            'email': emailController.text,
            'id': docID
                  };
// complete if else statement is given in the source code at the end
 await dUser.update(jsonData)
This is the map and we can see that a new key-value pair has been added to store the unique ID of each document. We can use the update method to update the existing values.

The difference between the update and the set method is that the set method will replace the whole document with new values while the update method will only update the specified values inside the document.

Let’s now try to update an existing value. See below example images.

flutter list view widget showing FLutter text widgets and flutter icon widgets

flutter textfield widgets to send data to firebase cloud firestore database

flutter list view with data fetched from firebase firestore

So this is how we can update the existing data.

Delete Data

For that, we’ll use the delete icon that is displayed on the right side of each item in the home page list view builder. We just need to create a reference to the existing document using. We’ve used the stored document ID for it. Then we’ve to call it’s delete method. See the below code:

 final docData = FirebaseFirestore.instance
                   .collection('users')
                    .doc(streamSnapshot.data!.docs[index]['id']); 
 await docData.delete();
We’ve tried it and the result was that this specific document gets deleted from the list and from the database as well.

So this is how we can easily create a Flutter Firebase CRUD app. Hope you have learned something new from this tutorial. If you do then don’t forget to share it with other Flutter developers.

Do ask if you still have any questions related to the implementation of the Flutter Firebase CRUD app. We’ll be very happy to provide answers to all your questions.

Click here for a video version of this tutorial.

GitHub Link to this Project

Click here to navigate to the GitHub repo of this project.

Flutter Firebase CRUD App Complete Source Code

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Firebase CRUD App',
      home: DisplayDataScreen(),
    );
  }
}
// creating UI that will gather data and send it to firestore with means performing Flutter Firebase CRUD operations 
class DisplayDataScreen extends StatefulWidget {
  DisplayDataScreen({Key? key}) : super(key: key);
  @override
  State<DisplayDataScreen> createState() => _DisplayDataScreenState();
}
class _DisplayDataScreenState extends State<DisplayDataScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
          onPressed: () {
            Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => SendOrUpdateData()));
          },
          backgroundColor: Colors.red.shade900,
          child: Icon(Icons.add)),
      appBar: AppBar(
        backgroundColor: Colors.red.shade900,
        centerTitle: true,
        title: Text(
          'Home',
          style: TextStyle(fontWeight: FontWeight.w300),
        ),
      ),
      body: StreamBuilder(
        stream: FirebaseFirestore.instance.collection('users').snapshots(),
        builder: (BuildContext context,
            AsyncSnapshot<QuerySnapshot> streamSnapshot) {
          return streamSnapshot.hasData
              ? ListView.builder(
                  padding: EdgeInsets.symmetric(vertical: 41),
                  itemCount: streamSnapshot.data!.docs.length,
                  itemBuilder: ((context, index) {
                    return Container(
                        margin: EdgeInsets.symmetric(horizontal: 20)
                            .copyWith(bottom: 10),
                        padding:
                            EdgeInsets.symmetric(horizontal: 10, vertical: 10),
                        decoration:
                            BoxDecoration(color: Colors.white, boxShadow: [
                          BoxShadow(
                              color: Colors.grey.shade300,
                              blurRadius: 5,
                              spreadRadius: 1,
                              offset: Offset(2, 2))
                        ]),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Row(
                              children: [
                                Icon(
                                  Icons.person,
                                  size: 31,
                                  color: Colors.red.shade300,
                                ),
                                SizedBox(width: 11),
                                Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Text(
                                      streamSnapshot.data!.docs[index]['name'],
                                      style: TextStyle(
                                          fontSize: 16,
                                          fontWeight: FontWeight.w500),
                                    ),
                                    Text(
                                      streamSnapshot.data!.docs[index]['age']
                                          .toString(),
                                      style: TextStyle(
                                          fontSize: 14,
                                          fontWeight: FontWeight.w300),
                                    ),
                                    Text(
                                      streamSnapshot.data!.docs[index]['email'],
                                      style: TextStyle(
                                          fontSize: 12,
                                          fontWeight: FontWeight.w400),
                                    ),
                                  ],
                                ),
                              ],
                            ),
                            Row(
                              children: [
                                GestureDetector(
                                  onTap: () {
                                    Navigator.of(context).push(
                                        MaterialPageRoute(
                                            builder: (context) =>
                                                SendOrUpdateData(
                                                  name: streamSnapshot.data!
                                                      .docs[index]['name'],
                                                  age: streamSnapshot
                                                      .data!.docs[index]['age']
                                                      .toString(),
                                                  email: streamSnapshot.data!
                                                      .docs[index]['email'],
                                                  id: streamSnapshot
                                                      .data!.docs[index]['id'],
                                                )));
                                  },
                                  child: Icon(
                                    Icons.edit,
                                    color: Colors.blue,
                                    size: 21,
                                  ),
                                ),
                                SizedBox(
                                  width: 10,
                                ),
                                GestureDetector(
                                  onTap: () async {
                                    final docData = FirebaseFirestore.instance
                                        .collection('users')
                                        .doc(streamSnapshot.data!.docs[index]
                                            ['id']);
                                    await docData.delete();
                                  },
                                  child: Icon(
                                    Icons.delete,
                                    color: Colors.red.shade900,
                                    size: 21,
                                  ),
                                )
                              ],
                            ),
                          ],
                        ));
                  }))
              : Center(
                  child: SizedBox(
                      height: 100,
                      width: 100,
                      child: CircularProgressIndicator()),
                );
        },
      ),
    );
  }
}
class SendOrUpdateData extends StatefulWidget {
  final String name;
  final String age;
  final String email;
  final String id;
  const SendOrUpdateData(
      {this.name = '', this.age = '', this.email = '', this.id = ''});
  @override
  State<SendOrUpdateData> createState() => _SendOrUpdateDataState();
}
class _SendOrUpdateDataState extends State<SendOrUpdateData> {
  TextEditingController nameController = TextEditingController();
  TextEditingController ageController = TextEditingController();
  TextEditingController emailController = TextEditingController();
  bool showProgressIndicator = false;

  @override
  void initState() {
    nameController.text = widget.name;
    ageController.text = widget.age;
    emailController.text = widget.email;
    super.initState();
  }

  @override
  void dispose() {
    nameController.dispose();
    ageController.dispose();
    emailController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.red.shade900,
        centerTitle: true,
        title: Text(
          'Send Data',
          style: TextStyle(fontWeight: FontWeight.w300),
        ),
      ),
      body: SingleChildScrollView(
        padding:
            EdgeInsets.symmetric(horizontal: 20).copyWith(top: 60, bottom: 200),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Name',
              style: TextStyle(fontWeight: FontWeight.w500),
            ),
            TextField(
              controller: nameController,
              decoration: InputDecoration(hintText: 'e.g. Zeeshan'),
            ),
            SizedBox(
              height: 20,
            ),
            Text(
              'Age',
              style: TextStyle(fontWeight: FontWeight.w500),
            ),
            TextField(
              controller: ageController,
              decoration: InputDecoration(hintText: 'e.g. 25'),
            ),
            SizedBox(
              height: 20,
            ),
            Text(
              'Email Address',
              style: TextStyle(fontWeight: FontWeight.w500),
            ),
            TextField(
              controller: emailController,
              decoration:
                  InputDecoration(hintText: 'e.g. zeerockyf5@gmail.com'),
            ),
            SizedBox(
              height: 40,
            ),
            MaterialButton(
              onPressed: () async {
                setState(() {});
                if (nameController.text.isEmpty ||
                    ageController.text.isEmpty ||
                    emailController.text.isEmpty) {
                  ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('Fill in all fields')));
                } else {
                  //reference to document
                  final dUser = FirebaseFirestore.instance
                      .collection('users')
                      .doc(widget.id.isNotEmpty ? widget.id : null);
                  String docID = '';
                  if (widget.id.isNotEmpty) {
                    docID = widget.id;
                  } else {
                    docID = dUser.id;
                  }
                  final jsonData = {
                    'name': nameController.text,
                    'age': int.parse(ageController.text),
                    'email': emailController.text,
                    'id': docID
                  };
                  showProgressIndicator = true;
                  if (widget.id.isEmpty) {
                  //create document and write data to firebase
                    await dUser.set(jsonData).then((value) {
                      nameController.text = '';
                      ageController.text = '';
                      emailController.text = '';
                      showProgressIndicator = false;
                      setState(() {});
                    });
                  } else {
                    await dUser.update(jsonData).then((value) {
                      nameController.text = '';
                      ageController.text = '';
                      emailController.text = '';
                      showProgressIndicator = false;
                      setState(() {});
                    });
                  }
                }
              },
              minWidth: double.infinity,
              height: 50,
              color: Colors.red.shade400,
              child: showProgressIndicator
                  ? CircularProgressIndicator(
                      color: Colors.white,
                    )
                  : Text(
                      'Submit',
                      style: TextStyle(
                          color: Colors.white,
                          fontSize: 16,
                          fontWeight: FontWeight.w300),
                    ),
            )
          ],
        ),
      ),
    );
  }
}

Conclusion

In conclusion, hope you now have a clear and in-depth practical understanding of how to properly implement the Flutter Firebase CRUD app. We’ll be looking forward to receiving your valuable feedback.

We’d be delighted to see you visit our other articles on Flutter app development. Thank you for reading this one.

Leave a Comment

Your email address will not be published. Required fields are marked *