We will fully understand how firebase firestore CRUD operations work and what collections and documents mean.
Once you create Firebase project, you will need to create database using Firestore database. In Firestore, database objects are refered as document and collection.
Collection is more like a table like in MySQL..
Document refers to the rows like in MySQL..
So first we need to create a instance of our table using below line
final CollectionReference _products =
FirebaseFirestore.instance.collection('products');
Now if you have multiple tables then you would create multiple references using the code like above.
Anyway, we would be able to do further operations on table products using _products instance.
Now let's take a look how we will do CRUD operations on the table products using _products. We will have below three lines of code.
And they are the heart of firebase operations.
await _products.add({"name": name, "price": price});
await _products.update({"name": name, "price": price});
await _products.doc(productId).delete();
We will create connection to the Firestore using StreamBuilder.
StreamBuilder(
stream: _products.snapshots(), //build connection
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length, //number of rows
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['price'].toString()),
trailing: SizedBox(
width: 100,
child: Row(
children: [
// Press this button to edit a single product
IconButton(
icon: const Icon(Icons.edit),
onPressed: () =>
_update(documentSnapshot)),
// This icon button is used to delete a single product
IconButton(
icon: const Icon(Icons.delete),
onPressed: () =>
_deleteProduct(documentSnapshot.id)),
],
),
),
),
);
},
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
)
In the above code _prodcuts.snapshots(), refers to the rows or the documents and stream of firestore data source.
And we use AsyncSnapshot<QuerySnapshot> streamSnapshot to get the actual data. So the rows could be accessed by using streamSnapshot.data
So streamSnapshot.data.docs would refer to the rows of the database. Once again in Firestore documents are equivalent to rows of MySQL
You might be interested in learning about
The complete code
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(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Firebase Firestore',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// text fields' controllers
final TextEditingController _nameController = TextEditingController();
final TextEditingController _priceController = TextEditingController();
final CollectionReference _products =
FirebaseFirestore.instance.collection('products');
Future<void> _create([DocumentSnapshot? documentSnapshot]) async {
await showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext ctx) {
return Padding(
padding: EdgeInsets.only(
top: 20,
left: 20,
right: 20,
bottom: MediaQuery.of(ctx).viewInsets.bottom + 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
TextField(
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
controller: _priceController,
decoration: const InputDecoration(
labelText: 'Price',
),
),
const SizedBox(
height: 20,
),
ElevatedButton(
child: const Text('Create'),
onPressed: () async {
final String name = _nameController.text;
final double? price =
double.tryParse(_priceController.text);
if (price != null) {
await _products.add({"name": name, "price": price});
_nameController.text = '';
_priceController.text = '';
Navigator.of(context).pop();
}
},
)
],
),
);
});
}
Future<void> _update([DocumentSnapshot? documentSnapshot]) async {
if (documentSnapshot != null) {
_nameController.text = documentSnapshot['name'];
_priceController.text = documentSnapshot['price'].toString();
}
await showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext ctx) {
return Padding(
padding: EdgeInsets.only(
top: 20,
left: 20,
right: 20,
bottom: MediaQuery.of(ctx).viewInsets.bottom + 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
TextField(
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
controller: _priceController,
decoration: const InputDecoration(
labelText: 'Price',
),
),
const SizedBox(
height: 20,
),
ElevatedButton(
child: const Text( 'Update'),
onPressed: () async {
final String name = _nameController.text;
final double? price =
double.tryParse(_priceController.text);
if (price != null) {
await _products
.doc(documentSnapshot!.id)
.update({"name": name, "price": price});
_nameController.text = '';
_priceController.text = '';
Navigator.of(context).pop();
}
},
)
],
),
);
});
}
Future<void> _delete(String productId) async {
await _products.doc(productId).delete();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('You have successfully deleted a product')));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Center(child: Text('Firebase Firestore')),
),
body: StreamBuilder(
stream: _products.snapshots(),
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['price'].toString()),
trailing: SizedBox(
width: 100,
child: Row(
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () =>
_update(documentSnapshot)),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () =>
_delete(documentSnapshot.id)),
],
),
),
),
);
},
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
// Add new product
floatingActionButton: FloatingActionButton(
onPressed: () => _create(),
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat
);
}
}
Flutter E-commerce App
Flutter 3.0 Master Class