Robin Alex Panicker

Sep 8, 20234 min

Integrating and Using Firestore in Flutter Apps

Firestore is a powerful NoSQL database offered by Firebase, a platform provided by Google. It's a perfect fit for building real-time, cloud-hosted applications.

In this article, we'll explore how to integrate Firestore into a Flutter application and build a complete CRUD (Create, Read, Update, Delete) address book application. By the end of this tutorial, you'll have a fully functional address book app that allows you to manage your contacts.

Prerequisites

Before we begin, ensure you have the following prerequisites:

  1. Flutter Environment: Make sure you have Flutter installed and set up on your development machine. You can get started with Flutter by following the official installation guide.

  2. Firebase Account: Create a Firebase account (if you don't have one) and set up a new project on the Firebase Console.

  3. FlutterFire Dependencies: We'll use the cloud_firestore package to interact with Firestore. Add the following dependency to your pubspec.yaml file:

dependencies:
 
flutter:
 
sdk: flutter
 
cloud_firestore: ^4.9.1

Run flutter pub get to fetch the package.

Setting up Firestore

  1. Firebase Project Configuration: In your Firebase project, go to the Firebase Console and click on "Project settings." Under the "General" tab, scroll down to the "Your apps" section and click on the "Firebase SDK snippet" icon (</>) for the web app. This will provide you with a configuration snippet containing your Firebase credentials.

  2. Initialize Firebase in Flutter: In your Flutter app, open the main.dart file and add the following code to initialize Firebase using the configuration snippet obtained in the previous step:

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

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

Building the Address Book App

Now, let's start building our address book app. We'll create a simple app with the following features:

  1. Display a list of contacts.

  2. Add a new contact.

  3. Edit an existing contact.

  4. Delete a contact.

Create a Firestore Collection

In Firestore, data is organized into collections and documents. For our address book app, let's create a collection named "contacts."

final CollectionReference contactsCollection = FirebaseFirestore.instance.collection('contacts');
 

Create a Model Class

We'll need a model class to represent our contact. Create a file named contact.dart and define the following class:

class Contact {
 
final String id;
 
final String name;
 
final String phoneNumber;
 

 
Contact({required this.id, required this.name, required this.phoneNumber});
 
}
 

Create a CRUD Service

Next, let's create a service to perform CRUD operations on our Firestore collection. Create a file named crud_service.dart and implement the following methods:

import 'package:cloud_firestore/cloud_firestore.dart';
 

 
class CrudService {
 
// Reference to the Firestore collection
 
final CollectionReference contactsCollection = FirebaseFirestore.instance.collection('contacts');
 

 
Future<void> addContact(String name, String phoneNumber) async {
 
await contactsCollection.add({'name': name, 'phoneNumber': phoneNumber});
 
}
 

 
Future<void> updateContact(String id, String name, String phoneNumber) async {
 
await contactsCollection.doc(id).update({'name': name, 'phoneNumber': phoneNumber});
 
}
 

 
Future<void> deleteContact(String id) async {
 
await contactsCollection.doc(id).delete();
 
}
 
}
 

Implementing UI

Now, let's create the user interface for our address book app using Flutter widgets. We'll create screens for listing contacts, adding a new contact, and editing an existing contact.

Listing Contacts

import 'package:flutter/material.dart';
 
import 'package:your_app_name/models/contact.dart';
 
import 'package:your_app_name/services/crud_service.dart';
 

 
class ContactListScreen extends StatelessWidget {
 
final CrudService crudService = CrudService();
 

 
@override
 
Widget build(BuildContext context) {
 
return Scaffold(
 
appBar: AppBar(title: Text('Contacts')),
 
body: StreamBuilder<QuerySnapshot>(
 
stream: crudService.contactsCollection.snapshots(),
 
builder: (context, snapshot) {
 
if (snapshot.hasError) {
 
return Text('Error: ${snapshot.error}');
 
}
 

 
if (snapshot.connectionState == ConnectionState.waiting) {
 
return CircularProgressIndicator();
 
}
 

 
final contacts = snapshot.data?.docs ?? [];
 

 
return ListView.builder(
 
itemCount: contacts.length,
 
itemBuilder: (context, index) {
 
final contact = Contact(
 
id: contacts[index].id,
 
name: contacts[index]['name'],
 
phoneNumber: contacts[index]['phoneNumber'],
 
);
 

 
return ListTile(
 
title: Text(contact.name),
 
subtitle: Text(contact.phoneNumber),
 
onTap: () {
 
// Navigate to contact details/edit screen
 
},
 
onLongPress: () {
 
// Delete contact
 
},
 
);
 
},
 
);
 
},
 
),
 
floatingActionButton: FloatingActionButton(
 
onPressed: () {
 
// Navigate to add contact screen
 
},
 
child: Icon(Icons.add),
 
),
 
);
 
}
 
}
 

Adding and Editing Contacts

import 'package:flutter/material.dart';
 
import 'package:your_app_name/models/contact.dart';
 
import 'package:your_app_name/services/crud_service.dart';
 

 
class AddEditContactScreen extends StatefulWidget {
 
final Contact? contact;
 

 
AddEditContactScreen({this.contact});
 

 
@override
 
_AddEditContactScreenState createState() => _AddEditContactScreenState();
 
}
 

 
class _AddEditContactScreenState extends State<AddEditContactScreen> {
 
final CrudService crudService = CrudService();
 
final _formKey = GlobalKey<FormState>();
 
late TextEditingController _nameController;
 
late TextEditingController _phoneNumberController;
 

 
@override
 
void initState() {
 
super.initState();
 
_nameController = TextEditingController(text: widget.contact?.name ?? '');
 
_phoneNumberController = TextEditingController(text: widget.contact?.phoneNumber ?? '');
 
}
 

 
@override
 
Widget build(BuildContext context) {
 
return Scaffold(
 
appBar: AppBar(
 
title: Text(widget.contact == null ? 'Add Contact' : 'Edit Contact'),
 
),
 
body: Form(
 
key: _formKey,
 
child: Column(
 
children: [
 
TextFormField(
 
controller: _nameController,
 
decoration: InputDecoration(labelText: 'Name'),
 
validator: (value) {
 
if (value == null || value.isEmpty) {
 
return 'Please enter a name';
 
}
 
return null;
 
},
 
),
 
TextFormField(
 
controller: _phoneNumberController,
 
decoration: InputDecoration(labelText: 'Phone Number'),
 
validator: (value) {
 
if (value == null || value.isEmpty) {
 
return 'Please enter a phone number';
 
}
 
return null;
 
},
 
),
 
ElevatedButton(
 
onPressed: () {
 
if (_formKey.currentState!.validate()) {
 
final name = _nameController.text;
 
final phoneNumber = _phoneNumberController.text;
 

 
if (widget.contact == null) {
 
// Add contact
 
} else {
 
// Update contact
 
}
 
}
 
},
 
child: Text(widget.contact == null ? 'Add Contact' : 'Save Changes'),
 
),
 
],
 
),
 
),
 
);
 
}
 
}
 

Conclusion

In this article, we've walked through the process of integrating Firestore into a Flutter app and building a complete CRUD address book application. You've learned how to set up Firestore, create a model class, implement CRUD operations, and create the user interface for listing, adding, and editing contacts.

This is just the beginning, and you can further enhance your app by adding authentication, search functionality, and more features. Firestore and Flutter provide a powerful combination for building modern and scalable mobile applications.

Happy coding!