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