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

0 0 votes
Article Rating
Subscribe
Notify of
guest
241 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments