Cloud Firestore helps us store data in the cloud. It supports offline mode so our app will work fine (write, read, listen to, and query data) whether device has internet connection or not, it automatically fetches changes from our database to Firebase Server. We can structure data in our ways to improve querying and fetching capabilities. This tutorial shows you a Flutter app that can do Firestore CRUD Operations with ListView widget.
Related Posts:
– How to integrate Firebase into Flutter App – Android Studio
– Flutter Navigator example – Send/Return data to/from new Screen
– Flutter ListView example with ListView.builder
Firebase Database: Flutter Firebase Database example – Firebase Database CRUD with ListView
Contents
Flutter Firestore Example Overview
We will build a Flutter App that supports showing, inserting, editing, deleting Notes from/to Cloud Firestore Database with ListView
:
Firebase Console for Firestore will be like:
You can find how to:
– use Flutter ListView at: Flutter ListView example with ListView.builder
– send/receive data between screens at: Flutter Navigator example – Send/Return data to/from new Screen
Cloud Firestore
Add Firestore to Flutter App
We’ve already had a Tutorial, please visit: How to integrate Firebase into Flutter App – Android Studio.
Initialize & Reference
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import 'package:cloud_firestore/cloud_firestore.dart'; // Access a Cloud Firestore instance from your Activity Firestore db = Firestore.instance; // Reference to a Collection CollectionReference notesCollectionRef = db.collection('notes'); // Reference to a Document in a Collection DocumentReference jsaDocumentRef = db.collection('notes').document('gkz'); // or DocumentReference jsaDocumentRef2 = db.document('notes/gkz'); // Hierarchical Data with Subcollection-Document in a Document DocumentReference androidTutRef = db .collection('notes').document('gkz') .collection('tutorials').document('flutterTutRef'); |
Create
Assume that our database structure is like:
1 2 3 4 5 6 7 8 9 10 11 |
notes_colection | |-----note-id_document | | | |------title_field | |------description_field | |-----note-id_document | | | |------title_field | |------description_field |
Using set()
to create or overwrite a single document:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Future<Note> createNote(String title, String description) async { final TransactionHandler createTransaction = (Transaction tx) async { final DocumentSnapshot ds = await tx.get(db.collection('notes').document()); var dataMap = new Map<String, dynamic>(); dataMap['title'] = '_title'; dataMap['description'] = '_description'; await tx.set(ds.reference, dataMap); return dataMap; }; return Firestore.instance.runTransaction(createTransaction).then((mapData) { return Note.fromMap(mapData); }).catchError((error) { print('error: $error'); return null; }); } |
Read
– get all Documents from Collection:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Stream<QuerySnapshot> getNoteList({int offset, int limit}) { Stream<QuerySnapshot> snapshots = db.collection('notes').snapshots(); if (offset != null) { snapshots = snapshots.skip(offset); } if (limit != null) { snapshots = snapshots.take(limit); } return snapshots; } // get itemList from Stream stream.listen((QuerySnapshot snapshot) { final List<Note> notes = snapshot.documents.map((documentSnapshot) => Note.fromMap(documentSnapshot.data)).toList(); }); |
Update
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Future<dynamic> updateNote(Note note) async { final TransactionHandler updateTransaction = (Transaction tx) async { final DocumentSnapshot ds = await tx.get(db.collection('notes').document('note-id')); await tx.update(ds.reference, note.toMap()); return {'updated': true}; }; return Firestore.instance .runTransaction(updateTransaction) .then((result) => result['updated']) .catchError((error) { print('error: $error'); return false; }); } |
Delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Future<dynamic> deleteNote(String id) async { final TransactionHandler deleteTransaction = (Transaction tx) async { final DocumentSnapshot ds = await tx.get(db.collection('notes').document(id)); await tx.delete(ds.reference); return {'deleted': true}; }; return Firestore.instance .runTransaction(deleteTransaction) .then((result) => result['deleted']) .catchError((error) { print('error: $error'); return false; }); } |
Practice
Set up Project
Follow these steps to add Firestore to the Project.
Project Structure
Data Model
lib/model/note.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Note { String _id; String _title; String _description; Note(this._id, this._title, this._description); Note.map(dynamic obj) { this._id = obj['id']; this._title = obj['title']; this._description = obj['description']; } String get id => _id; String get title => _title; String get description => _description; Map<String, dynamic> toMap() { var map = new Map<String, dynamic>(); if (_id != null) { map['id'] = _id; } map['title'] = _title; map['description'] = _description; return map; } Note.fromMap(Map<String, dynamic> map) { this._id = map['id']; this._title = map['title']; this._description = map['description']; } } |
Firebase Firestore Data Service
lib/service/firebase_firestore_service.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter_firebase/model/note.dart'; final CollectionReference noteCollection = Firestore.instance.collection('notes'); class FirebaseFirestoreService { static final FirebaseFirestoreService _instance = new FirebaseFirestoreService.internal(); factory FirebaseFirestoreService() => _instance; FirebaseFirestoreService.internal(); Future<Note> createNote(String title, String description) async { final TransactionHandler createTransaction = (Transaction tx) async { final DocumentSnapshot ds = await tx.get(noteCollection.document()); final Note note = new Note(ds.documentID, title, description); final Map<String, dynamic> data = note.toMap(); await tx.set(ds.reference, data); return data; }; return Firestore.instance.runTransaction(createTransaction).then((mapData) { return Note.fromMap(mapData); }).catchError((error) { print('error: $error'); return null; }); } Stream<QuerySnapshot> getNoteList({int offset, int limit}) { Stream<QuerySnapshot> snapshots = noteCollection.snapshots(); if (offset != null) { snapshots = snapshots.skip(offset); } if (limit != null) { snapshots = snapshots.take(limit); } return snapshots; } Future<dynamic> updateNote(Note note) async { final TransactionHandler updateTransaction = (Transaction tx) async { final DocumentSnapshot ds = await tx.get(noteCollection.document(note.id)); await tx.update(ds.reference, note.toMap()); return {'updated': true}; }; return Firestore.instance .runTransaction(updateTransaction) .then((result) => result['updated']) .catchError((error) { print('error: $error'); return false; }); } Future<dynamic> deleteNote(String id) async { final TransactionHandler deleteTransaction = (Transaction tx) async { final DocumentSnapshot ds = await tx.get(noteCollection.document(id)); await tx.delete(ds.reference); return {'deleted': true}; }; return Firestore.instance .runTransaction(deleteTransaction) .then((result) => result['deleted']) .catchError((error) { print('error: $error'); return false; }); } } |
UI
List of Items Screen
lib/ui/listview_note.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_firebase/service/firebase_firestore_service.dart'; import 'package:flutter_firebase/model/note.dart'; import 'package:flutter_firebase/ui/note_screen.dart'; class ListViewNote extends StatefulWidget { @override _ListViewNoteState createState() => new _ListViewNoteState(); } class _ListViewNoteState extends State<ListViewNote> { List<Note> items; FirebaseFirestoreService db = new FirebaseFirestoreService(); StreamSubscription<QuerySnapshot> noteSub; @override void initState() { super.initState(); items = new List(); noteSub?.cancel(); noteSub = db.getNoteList().listen((QuerySnapshot snapshot) { final List<Note> notes = snapshot.documents .map((documentSnapshot) => Note.fromMap(documentSnapshot.data)) .toList(); setState(() { this.items = notes; }); }); } @override void dispose() { noteSub?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'grokonez Firestore Demo', home: Scaffold( appBar: AppBar( title: Text('grokonez Firestore Demo'), centerTitle: true, backgroundColor: Colors.blue, ), body: Center( child: ListView.builder( itemCount: items.length, padding: const EdgeInsets.all(15.0), itemBuilder: (context, position) { return Column( children: <Widget>[ Divider(height: 5.0), ListTile( title: Text( '${items[position].title}', style: TextStyle( fontSize: 22.0, color: Colors.deepOrangeAccent, ), ), subtitle: Text( '${items[position].description}', style: new TextStyle( fontSize: 18.0, fontStyle: FontStyle.italic, ), ), leading: Column( children: <Widget>[ Padding(padding: EdgeInsets.all(10.0)), CircleAvatar( backgroundColor: Colors.blueAccent, radius: 15.0, child: Text( '${position + 1}', style: TextStyle( fontSize: 22.0, color: Colors.white, ), ), ), IconButton( icon: const Icon(Icons.remove_circle_outline), onPressed: () => _deleteNote(context, items[position], position)), ], ), onTap: () => _navigateToNote(context, items[position]), ), ], ); }), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () => _createNewNote(context), ), ), ); } void _deleteNote(BuildContext context, Note note, int position) async { db.deleteNote(note.id).then((notes) { setState(() { items.removeAt(position); }); }); } void _navigateToNote(BuildContext context, Note note) async { await Navigator.push( context, MaterialPageRoute(builder: (context) => NoteScreen(note)), ); } void _createNewNote(BuildContext context) async { await Navigator.push( context, MaterialPageRoute(builder: (context) => NoteScreen(Note(null, '', ''))), ); } } |
Item Screen
lib/ui/note_screen.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import 'package:flutter/material.dart'; import 'package:flutter_firebase/model/note.dart'; import 'package:flutter_firebase/service/firebase_firestore_service.dart'; class NoteScreen extends StatefulWidget { final Note note; NoteScreen(this.note); @override State<StatefulWidget> createState() => new _NoteScreenState(); } class _NoteScreenState extends State<NoteScreen> { FirebaseFirestoreService db = new FirebaseFirestoreService(); TextEditingController _titleController; TextEditingController _descriptionController; @override void initState() { super.initState(); _titleController = new TextEditingController(text: widget.note.title); _descriptionController = new TextEditingController(text: widget.note.description); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Note')), body: Container( margin: EdgeInsets.all(15.0), alignment: Alignment.center, child: Column( children: <Widget>[ TextField( controller: _titleController, decoration: InputDecoration(labelText: 'Title'), ), Padding(padding: new EdgeInsets.all(5.0)), TextField( controller: _descriptionController, decoration: InputDecoration(labelText: 'Description'), ), Padding(padding: new EdgeInsets.all(5.0)), RaisedButton( child: (widget.note.id != null) ? Text('Update') : Text('Add'), onPressed: () { if (widget.note.id != null) { db .updateNote( Note(widget.note.id, _titleController.text, _descriptionController.text)) .then((_) { Navigator.pop(context); }); } else { db.createNote(_titleController.text, _descriptionController.text).then((_) { Navigator.pop(context); }); } }, ), ], ), ), ); } } |
Hi, first of all, awesome post, something which I was looking for. I just have one query, how can I manage(CRUD) a field of type array in firestore. I want to store dates for user for a particular year, and feel arrays should be the datatype to use, do you think there another way of storing this information?
Thanks for the post, awaiting your response.
All throughout the instructions the direction is to put the darts under /lib/…
##
List of Items Screen
lib/ui/listview_note.dart
##
but in the dart files the imports statements reference them otherwise:
##
import ‘package:flutter_firebase/model/note.dart’;
import ‘package:flutter_firebase/ui/note_screen.dart’;
##
while i have put the code under /lib/ and changed the import references, the call in main.dart to:
##
‘package:flutter_firebase/lib/ui/listveiw_note.dart’;
##
will not compile for me .. and i cannot get it to?? Feedback from AS suggest it knows nothing about a package called ‘flitter_firebase/lib’ ???
It works like a charm! Thanks for sharing!
I refactored this solution a bit so it’s easier to re-use in other projects:
https://medium.com/@frankpaepens/flutter-firestore-improved-example-85ce70e0d857
Hi Frank Paepens,
We are so happy when you can make the world better 🙂
Best Regards,
grokonez.
thanks, u help me a lot
what is the function of
noteSub?.cancel();