In this part of the tutorial series, we will focus more in the backend. More precisely, I will teach you how to integrate Firebase Firestore into your Flutter app. If you are new to Firestore, let me give you some background information. It’s a cloud-hosted NoSQL database which allows you to store, sync, and query data in real-time. If you have experience in relational database, that’s good but firestore is not exactly the same. You can use it for the apps that require dynamic data or real-time updates, such as social media platforms, chat applications, or any app with collaborative features.
Alright, we’ll walk through how to set up Firestore, perform some basic CRUD (Create, Read, Update, Delete) operations, and enable real-time data syncing with examples.
After this tutorial, you will learn:
- How to configure Firestore in your Flutter app.
- Basic CRUD operations in Firestore.
- Setting up real-time listeners to update data dynamically.
Setting Up Firestore in Firebase Console
We begin with the basic setup. In order to use Firestore with Flutter, you need to have some basic setup in the Firebase Console.
Step 1: Enable Firestore in Firebase Console
- Go to the Firebase Console and select your project.
- In the left sidebar, click on Firestore Database under the Build section.
- Click Create Database. (The latest version with the option of “Ask Gemini”)
- Select the location and select the test mode for security rules during development (you can update these later for production).
After the above steps, the setup should be completed. Your Firestore is now ready to use!
Step 2: Add Firestore to Your Flutter App
Now your environment is ready, next thing you need to do is make some configurations in the Flutter project. In order to do this, you need to add the necessary dependencies in the pubspec.yaml
file. Go back to your Flutter project. And then open your pubspec.yaml
file and add the following dependencies. Remember to double check the latest version by the time being.
dependencies:
firebase_core: latest_version
cloud_firestore: latest_version
After adding the dependencies in the file, you need to run the below Flutter command through terminal.
flutter pub get
Next go to your lib/main.dart
, initialize Firebase before running your app. Update the main.dart
file as follows:
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FirestoreExample(),
);
}
}
Step 3: Basic Firestore CRUD Operations
Good job! Once you finished all the steps above, you should be able to interact with the Firestore. Let’s first go through the basics of Create, Read, Update, and Delete operations (CRUD).
Unlike relational database, the concept of table do not apply to Firestore. It use the concept of “collections” for data storage and retrieve in the “documents”. Let’s try the below example to get familiar with that.
Example of Writing Data to Firestore (Create)
In below example, I try to create a simple form with Flutter to let the apps users adding data to the Firestore.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreExample extends StatelessWidget {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final TextEditingController _controller = TextEditingController();
Future<void> _addTask(String task) async {
await _firestore.collection("tasks").add({
"task": task,
"timestamp": FieldValue.serverTimestamp(),
});
_controller.clear();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Firestore Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Enter a task'),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
_addTask(_controller.text);
},
child: Text('Add Task'),
),
],
),
),
);
}
}
First of all, we created a TextField
to capture the task name. And then the _addTask()
method is used to write the task to Firestore under a tasks
collection. After all, the timestamp field automatically records the server time when the document is created.
Reading Data from Firestore (Read)
To display the data stored in Firestore, you can set up a stream to read data in real-time.
class TaskList extends StatelessWidget {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Task List'),
),
body: StreamBuilder(
stream: _firestore.collection('tasks').orderBy('timestamp').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) {
return Center(child: Text('No tasks available'));
}
return ListView(
children: snapshot.data!.docs.map((task) {
return ListTile(
title: Text(task['task']),
);
}).toList(),
);
},
),
);
}
}
- StreamBuilder listens for real-time changes in the
tasks
collection. - When new data is added, updated, or deleted, the list automatically refreshes to reflect the changes.
3. Updating Data in Firestore (Update)
Updating a document in Firestore is simple. You’ll need the document’s reference to update it.
class UpdateTask extends StatelessWidget {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final String taskId;
final TextEditingController _controller = TextEditingController();
UpdateTask({required this.taskId});
Future<void> _updateTask(String newTask) async {
await _firestore.collection('tasks').doc(taskId).update({
"task": newTask,
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Update Task')),
body: Column(
children: [
TextField(controller: _controller),
ElevatedButton(
onPressed: () {
_updateTask(_controller.text);
},
child: Text('Update Task'),
),
],
),
);
}
}
In this example:
- We fetch the document by its
taskId
and update the task using theupdate()
method. - This allows real-time updating of task data in the Firestore database.
4. Deleting Data from Firestore (Delete)
To delete a task from Firestore, we’ll need the document’s reference, similar to updating.
Future<void> _deleteTask(String taskId) async {
await _firestore.collection('tasks').doc(taskId).delete();
}
This function deletes a document from the tasks
collection based on its taskId
.
Step 4: Real-Time Data Syncing with Firestore
One of Firestore’s most powerful features is its real-time syncing. Any changes made to the database (additions, deletions, updates) are immediately reflected in your app.
1. Listening for Real-Time Updates
We already used a StreamBuilder in the previous step to read tasks in real-time. This ensures that any data modifications on Firestore are instantly updated in the app, providing a seamless user experience.
To add additional functionality, such as real-time notifications or triggers, you can customize how the app listens to changes and responds dynamically.
2. Firestore Offline Capabilities
Firestore also supports offline syncing. This means your app will continue to work even if the user loses internet connectivity. Changes made offline will be automatically synchronized when the connection is restored.
Firestore handles offline caching and syncing by default, so you don’t need to configure anything for this to work.
Step 5: Firestore Security Rules
During development, we often use test mode for Firestore security rules, but for production, you’ll need to set up stricter rules to protect your data.
1. Configuring Firestore Rules
In the Firebase Console, go to the Firestore Database section and click on Rules. You can write custom rules to control who can read and write data.
For example, to allow only authenticated users to write to the database:
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{task} {
allow read, write: if request.auth != null;
}
}
}
This ensures that only authenticated users can modify tasks.
Conclusion: Powering Your App with Firestore
With Firestore integrated into your Flutter app, you’ve gained the ability to perform real-time data operations and provide a dynamic, responsive experience to your users. By utilizing CRUD operations, real-time listeners, and Firestore’s offline capabilities, you can build robust apps that handle collaborative tasks, data syncing, and more.
In the next tutorial, we’ll explore Firebase Authentication in greater detail, diving into social logins (like Google and Facebook) and how to manage user sessions. Keep coding, and enjoy building your app!