FireStore CRUD in Flutter - Social Application

Last updated Apr 06, 2020


In This post we are going to implement social posting application with FireStore database.

This Example we are implementing all CRUD operations.

What is Firestore Database?

Firestore is a real time database that stores collections and documents. A collection stores documents and a document is made up of json. There's no predefined data structure, you can add and remove fields as you please without having to do migration steps. It's known as a NoSQL database. This tutorial will not cover the way you plan your database for your application or what kind of structures you should use. We already have a Users collection, we'll add a new Posts collection where we will store the post data.

 

Let's get Started.

Step 1: Create Flutter Application.

Step 2: Integrate Firebase database Refer My Previous post Integrate Firebase with Flutter.

Step 3: Add required dependencies

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^0.13.4+2
  font_awesome_flutter: ^8.8.1
  image_picker: ^0.6.4
  firebase_storage: ^3.1.5

 

Step 4:

Create Homepage to display all posts.

SocialPostFireStore.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'add_post.dart';
import 'constants.dart';
import 'details_post.dart';

class SocialPostFireStore extends StatelessWidget {
  final db = Firestore.instance;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: app_color,
      appBar: AppBar(title: Text("Social App"),backgroundColor: app_color,elevation: 0.0,),
      floatingActionButton: FloatingActionButton(
        mini: true,
        backgroundColor: app_color,
        onPressed: (){
          Navigator.push(context, MaterialPageRoute(builder: (context)  {
            return AddPost();
          },));
        },
      child: Icon(Icons.add,color: Colors.white,),),
      body: ListView(
        padding: EdgeInsets.only(left: 5,right: 5,bottom: 20),
        children: [
          SizedBox(height: 5.0),
          StreamBuilder(

              stream: db.collection('Posts').orderBy("createdtm",descending: true).snapshots(),
              builder: (context, snapshot) {


                if (snapshot.hasData) {
                  return Column(
                    children: snapshot.data.documents.map((doc) {
                      print(doc.data);
                      return GestureDetector(
                        onTap: (){
                          Navigator.push(context, MaterialPageRoute(builder: (context)  {
                            return PostDetails(data:doc.data['post']);
                          },));
                        },
                        child: Card(
                          shape:RoundedRectangleBorder(
                            borderRadius: BorderRadius.all(Radius.circular(8)
                            ,)
                          ),
                          color: Colors.white,
                          elevation: 1,
                          shadowColor: Colors.amberAccent,
                          child: Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Row(
                                  mainAxisAlignment: MainAxisAlignment.start,
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Container(
                                      width: 40.0,
                                      height: 40.0,
                                      decoration: new BoxDecoration(
                                        shape: BoxShape.circle,
                                        image: new DecorationImage(
                                            fit: BoxFit.fill,
                                            image: new NetworkImage(
                                                "https://pbs.twimg.com/profile_images/916384996092448768/PF1TSFOE_400x400.jpg")),
                                      ),
                                      margin: const EdgeInsets.symmetric(horizontal: 8.0),
                                    ),
                                    (doc.data['post']==null)?Container():
                                    Expanded(child: Text(doc.data['post']['title'],style: TextStyle(color: app_color,fontSize: 18,fontWeight: FontWeight.bold,letterSpacing:1),))
                                  ],
                                ),
                                (doc.data['post']==null)?Container():
                                postInfo(doc.documentID,doc.data,context)
                              ],
                            ),
                          ),
                        ),
                      );
                      ListTile(
                        title: Text(doc.data['title'].toString()),

                        trailing: IconButton(
                          icon: Icon(Icons.cancel),
                          onPressed: () async {
                            await db
                                .collection('Posts')
                                .document(doc.documentID)
                                .delete();
                          },
                        ),
                      );
                    }).toList(),
                  );
                } else {
                  return SizedBox();
                }
              }),
        ],
      ),
    );
  }


  postInfo(documentID,data,context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 10),
      child: Column(
        children: [
         Card(
           elevation: 0.2,
           margin: EdgeInsets.all(2),
           child: Image.network(data['post']['img'],fit: BoxFit.fitWidth,height: 120,),
         )  ,
         SizedBox(height: 10,),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8),
            child: Text(data['post']['desc'],maxLines:3,overflow: TextOverflow.ellipsis,style: TextStyle(
              letterSpacing: 0.2,
              color: Colors.indigoAccent,fontSize: 14
            ),),
          ),
         SizedBox(height: 10,),
         Divider(height: 2,color: app_color,thickness: 1,),
         SizedBox(height: 10,),
         Container(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [

              Row(
                children: [
                  Padding(
                    padding: const EdgeInsets.only(left:8.0),
                    child: Row(
                      children: [
                        Text(data['post']['like'].toString(),style: TextStyle(color: Colors.amber),),
                        IconButton(

                            icon:  FaIcon(FontAwesomeIcons.solidHandPointUp,color: Colors.amber,),

                            onPressed: (){

                              data['post']['like']=  data['post']['like']+1;
                              db.collection("Posts").document(documentID).updateData(data);
                        }),
                      ],
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.only(left:8.0),
                    child: Row(
                      children: [
                        Text(data['post']['dislike'].toString(),style: TextStyle(color: Colors.orange),),
                        IconButton(

                            icon:  FaIcon(FontAwesomeIcons.solidHandPointDown,color: Colors.orange,),

                            onPressed: (){
                              data['post']['dislike']=  data['post']['dislike']+1;
                              db.collection("Posts").document(documentID).updateData(data).catchError((ex){print(ex);});
                            }),
                      ],
                    ),
                  ),
                ],
              ),
                PopupMenuButton(itemBuilder: (context) {
                  return >[

                    new PopupMenuItem( value: 2, child: new Text('Delete')),
                  ];
                },icon: Icon(Icons.more_vert,color: Colors.red,),onSelected: (value) => {
                db.collection("Posts").document(documentID).delete()
                },),

              ],
            ),
           decoration: new BoxDecoration(
             borderRadius: BorderRadius.only(topLeft: Radius.circular(10),bottomRight: Radius.circular(10)),
             border: Border.all(color: app_color,width: 1)
              ),

         )
        ],
      ),
    );
  }
}

 

Step 5: Create addPost file

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'constants.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as Path;

class AddPost extends StatefulWidget{
  var scaffole_key= GlobalKey();
  @override
  State createState() {
    // TODO: implement createState
    return AddPostState();
  }

}
class AddPostState extends State{
  final db = Firestore.instance;
  TextEditingController titleCtrl=TextEditingController();
  TextEditingController descCtrl=TextEditingController();
  String _uploadedFileURL;
  File  _image;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      key: widget.scaffole_key,
      appBar: AppBar(elevation: 0.0,backgroundColor: app_color,title: Text("Add Post"),),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListView(
          children: [
            SizedBox(height: 10,),
            TextFormField(
              controller: titleCtrl,
              decoration: new InputDecoration(
                labelText: "Enter Post Title",
                fillColor: Colors.white,
                focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(color: app_color,width: 2),borderRadius: BorderRadius.only(bottomRight: Radius.circular(10),topLeft:  Radius.circular(10))
                ),
                border: new OutlineInputBorder(
                  borderRadius: new BorderRadius.circular(5.0),
                  borderSide: new BorderSide(
                  ),
                ),
                //fillColor: Colors.green
              ),
              validator: (val) {
                if(val.length==0) {
                  return "Title cannot be empty";
                }else{
                  return null;
                }
              },
              keyboardType: TextInputType.emailAddress,
              style: new TextStyle(
                fontFamily: "Poppins",
              ),
            ),
            SizedBox(height: 20,),
            TextFormField(
              minLines: 5,
              maxLines: 8,
              controller: descCtrl,

              decoration: new InputDecoration(
                focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(color: app_color,width: 2),borderRadius: BorderRadius.only(bottomRight: Radius.circular(10),topLeft:  Radius.circular(10))
                ),
                labelText: "Enter Post Description",
                fillColor: Colors.white,

                border: new OutlineInputBorder(
                  borderRadius: new BorderRadius.circular(5.0),
                  borderSide: new BorderSide(
                  ),
                ),
                //fillColor: Colors.green
              ),
              validator: (val) {
                if(val.length==0) {
                  return "Description cannot be empty";
                }else{
                  return null;
                }
              },
              keyboardType: TextInputType.emailAddress,
              style: new TextStyle(
                fontFamily: "Poppins",
              ),
            ),

             SizedBox(height: 10,),
            Card(
              elevation: 0.2,
              margin: EdgeInsets.all(2),

            )  ,
            SizedBox(height: 10,),
            Container(
              padding: EdgeInsets.all(5),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.only(bottomRight: Radius.circular(10),topLeft:  Radius.circular(10)),
                border: Border.all(color: Colors.amber,width: 1)
              ),
              child: Column(
                children: [
                  _image == null
                      ? IconButton(
                    iconSize: 40,
                    icon: Icon(Icons.camera_alt),
                    onPressed: chooseFile,
                    color: Colors.amber,
                  )
                      : IconButton(
                    iconSize: 20,
                    icon: Icon(Icons.clear),
                    onPressed: (){
                      setState(() {
                        _image=null;
                        _uploadedFileURL="";
                      });
                    },
                    color: Colors.red,
                  ),
                  Text('Selected Image'),
                  _image != null
                      ? Image.asset(
                    _image.path,
                    height: 150,
                  )
                      : Container(height: 150),


                ],
              ),
            ),
            SizedBox(height: 10,),
            Divider(height: 2,color: app_color,thickness: 1,),
            MaterialButton(
                color: app_color,
                textColor: Colors.white,
                child: Text("Submit"),
                onPressed: (){
if(titleCtrl.text.isEmpty)
  {
    widget.scaffole_key.currentState.showSnackBar(SnackBar(content: Text("Please enter Post title")));

    return ;
  }

              Mapposthm=new Map();
              Maphm=new Map();
              hm.putIfAbsent("title", () => titleCtrl.text);
              hm.putIfAbsent("desc", () => descCtrl.text);
              hm.putIfAbsent("id", () => "");
              hm.putIfAbsent("img", () => _uploadedFileURL);
              hm.putIfAbsent("dislike", () => 0);
              hm.putIfAbsent("like", () => 0);
              posthm.putIfAbsent("post", () => hm);
              posthm.putIfAbsent("createdtm", () => Timestamp.now());
            db.collection("Posts").reference().add(posthm);
               Navigator.pop(context);
            })


          ],
        ),
      ),
    );
  }
  Future chooseFile() async {
    await ImagePicker.pickImage(source: ImageSource.gallery).then((image) {
      setState(() {
        _image=image;
      });

      uploadFile(_image);
    });
  }

  Future uploadFile(_image) async {
    StorageReference storageReference = FirebaseStorage.instance
        .ref()
        .child('posts/${Path.basename(_image.path)}}');
    StorageUploadTask uploadTask = storageReference.putFile(_image);
    await uploadTask.onComplete;
    print('File Uploaded');
    storageReference.getDownloadURL().then((fileURL) {

      print("Uploaded file $fileURL");
      setState(() {
        _uploadedFileURL = fileURL;
      });


    });
  }

  Future delete(_image) async {
    StorageReference storageReference = FirebaseStorage.instance
        .ref()
        .child('posts/${Path.basename(_image.path)}}');
    storageReference.delete();

    print('File Uploaded');

  }
}

 

Step 6: Create Details page

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

import 'constants.dart';

class PostDetails extends StatelessWidget{
  PostDetails({this.data});
  var data;
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(elevation: 0.0,backgroundColor: app_color,),
      body: Padding(
        padding: const EdgeInsets.all(12.0),
        child: ListView(
          children: [
            Expanded(child: Text(data['title'],style: TextStyle(color: Colors.red,fontSize: 18,fontWeight: FontWeight.bold,letterSpacing:1),)),
            SizedBox(height: 10,),

            Card(
                        elevation: 0.2,
                        margin: EdgeInsets.all(2),
                        child: Image.network(data['img'],fit: BoxFit.fitWidth,),
                  ),
      SizedBox(height: 10,),
      Container(
      width: 200,
      decoration: BoxDecoration(border: Border.all(color: Colors.amber),borderRadius: BorderRadius.all(Radius.circular(5))),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Padding(
            padding: const EdgeInsets.only(left:8.0),
            child: Row(
              children: [
                Text(data['like'].toString(),style: TextStyle(color: Colors.amber),),
                IconButton(

                  icon:  FaIcon(FontAwesomeIcons.solidHandPointUp,color: Colors.amber,),

                )
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(left:8.0),
            child: Row(
              children: [
                Text(data['dislike'].toString(),style: TextStyle(color: Colors.orange),),
                IconButton(

                  icon:  FaIcon(FontAwesomeIcons.solidHandPointDown,color: Colors.orange,),

                ),
              ],
            ),
          ),
        ],
      ),
    ),

                  SizedBox(height: 10,),
                  Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 8),
                  child: Text(data['desc'],style: TextStyle(
                      letterSpacing: 0.2,
                      color: Colors.indigoAccent,fontSize: 14
                  ),),
                  ),
                  SizedBox(height: 10,),
                  Divider(height: 2,color: app_color,thickness: 1,),


          ],
        ),
      ),
    );
  }

}

 

Step 7: Update main dart file

import 'package:flutter/material.dart';

import 'delete_firestore.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Social App',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: SocialPostFireStore(),
    );
  }
}

 

Step 8: Run Application 


Article Contributed By :
https://www.rrtutors.com/site_assets/profile/assets/img/avataaars.svg

305 Views

Subscribe For Daily Updates

Flutter Questions
Android Questions