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.
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"
2.1. Download the JSON File
Download the services JSON file. It contains the information to link your firebase project to your Flutter project.
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.
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'
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'
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
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.
Step 3: Create Firestore Database
For that, we’ve to click on create a database as shown in the below image.
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.
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.
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.
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
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 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();
@override void dispose() { nameController.dispose(); ageController.dispose(); emailController.dispose(); super.dispose(); }
TextField( controller: nameController // )
Flutter Material Button
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(() {}); }); } },
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()
streamSnapshot.hasData ? show list of data : show circular progress indicator
Flutter List View Builder
itemCount: streamSnapshot.data!.docs.length
streamSnapshot.data!.docs[index]['name']
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);
Step 2
if (widget.id.isNotEmpty) { docID = widget.id; } else { docID = dUser.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)
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.
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();
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.