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
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
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:
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:
FuturecreateNote(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 (); 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:
StreamgetNoteList({int offset, int limit}) { Stream 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 notes = snapshot.documents.map((documentSnapshot) => Note.fromMap(documentSnapshot.data)).toList(); });
Update
FutureupdateNote(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
FuturedeleteNote(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
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; MaptoMap() { var map = new Map (); if (_id != null) { map['id'] = _id; } map['title'] = _title; map['description'] = _description; return map; } Note.fromMap(Map map) { this._id = map['id']; this._title = map['title']; this._description = map['description']; } }
Firebase Firestore Data Service
lib/service/firebase_firestore_service.dart
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(); FuturecreateNote(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 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 getNoteList({int offset, int limit}) { Stream snapshots = noteCollection.snapshots(); if (offset != null) { snapshots = snapshots.skip(offset); } if (limit != null) { snapshots = snapshots.take(limit); } return snapshots; } Future 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 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
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{ List items; FirebaseFirestoreService db = new FirebaseFirestoreService(); StreamSubscription noteSub; @override void initState() { super.initState(); items = new List(); noteSub?.cancel(); noteSub = db.getNoteList().listen((QuerySnapshot snapshot) { final List 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: 'ozenero Firestore Demo', home: Scaffold( appBar: AppBar( title: Text('ozenero 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: [ 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: [ 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
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 StatecreateState() => new _NoteScreenState(); } class _NoteScreenState extends State { 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: [ 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); }); } }, ), ], ), ), ); } }