Flutter Firestore example – Firebase Firestore CRUD with ListView

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:

flutter-firebase-firestore-example-crud-listview-overview

Firebase Console for Firestore will be like:

flutter-firebase-firestore-example-crud-listview-firebase-console-result

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:

Future 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();
    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:

Stream getNoteList({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
Future 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
Future 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

flutter-firebase-firestore-example-crud-listview-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;

  Map toMap() {
    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();

  Future 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 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
  State createState() => 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);
                  });
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

Source Code

flutter_firebase_firestore

12 thoughts on “Flutter Firestore example – Firebase Firestore CRUD with ListView”

  1. 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.

  2. 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’ ???

  3. What i don’t understood is if truth be told how you are no longer really a lot more neatly-liked than you may be right now. You are very intelligent. You realize thus considerably in the case of this matter, made me for my part consider it from a lot of various angles. Its like women and men don’t seem to be interested unless it is something to do with Woman gaga! Your individual stuffs outstanding. At all times take care of it up!

Leave a Reply

Your email address will not be published. Required fields are marked *