👉Step 1: Create model class

We’ll go through the app view by view and add what we need.

Our application will need a Product model so under models we will create productModel.dart file.

This file contains 2 methods :

Product.fromMap(Map snapshot, String id): When data is fetched from Firebase, it is in JSON format. this method allows us to map data from JSON format to our Product format.

toJson() : The toJson() does the opposite which is to map the data back into JSON format before we upload into Firebase.

class Product {

String id;

String price;

String name;

String img;



Product({this.id, this.price, this.name,this.img});



Product.fromMap(Map snapshot,String id) :

id = id ?? '',

price = snapshot['price'] ?? '',

name = snapshot['name'] ?? '',

img = snapshot['img'] ?? '';



toJson() {

return {

"price": price,

"name": name,

"img": img,

};

}

}

👉Step 2: Create Api class

Under our Services directory will perform all the Network work. we will Create an Api class that will request /Read/Delete/ update data from Firebase. That class will contain different methods like fetching data as a stream , getting the document by id, removing/deleting a document …

class Api{

final Firestore _db = Firestore.instance;

final String path;

CollectionReference ref;



Api( this.path ) {

ref = _db.collection(path);

}



Future<QuerySnapshot> getDataCollection() {

return ref.getDocuments() ;

}

Stream<QuerySnapshot> streamDataCollection() {

return ref.snapshots() ;

}

Future<DocumentSnapshot> getDocumentById(String id) {

return ref.document(id).get();

}

Future<void> removeDocument(String id){

return ref.document(id).delete();

}

Future<DocumentReference> addDocument(Map data) {

return ref.add(data);

}

Future<void> updateDocument(Map data , String id) {

return ref.document(id).updateData(data) ;

}





}

Now to hook it up. As mentioned we will not be providing a bunch of models and services at the beginning of our app at global context scale. Instead, we’ll inject it using the locator setup in locator.dart

void setupLocator() {

locator.registerLazySingleton(() => Api('products'));

}

👉Step 3: Create CRUD Model

The CRUD Model will use the Api class to Handle the different operations. Under viewmodels create the CRUDModel.dart file and add the different functions needed. This model (as well as every other service and model) will be injected using the locator.

class CRUDModel extends ChangeNotifier {

Api _api = locator<Api>();



List<Product> products;





Future<List<Product>> fetchProducts() async {

var result = await _api.getDataCollection();

products = result.documents

.map((doc) => Product.fromMap(doc.data, doc.documentID))

.toList();

return products;

}



Stream<QuerySnapshot> fetchProductsAsStream() {

return _api.streamDataCollection();

}



Future<Product> getProductById(String id) async {

var doc = await _api.getDocumentById(id);

return Product.fromMap(doc.data, doc.documentID) ;

}





Future removeProduct(String id) async{

await _api.removeDocument(id) ;

return ;

}

Future updateProduct(Product data,String id) async{

await _api.updateDocument(data.toJson(), id) ;

return ;

}



Future addProduct(Product data) async{

var result = await _api.addDocument(data.toJson()) ;



return ;



}





}

Now we register it in the locator file.

void setupLocator() {

locator.registerLazySingleton(() => Api('products'));

locator.registerLazySingleton(() => CRUDModel()) ;

}

that should be all for the logic now we will move to creating our UI and consuming these services.

👉Step 4: Routing and main configuration

Our app will have 4 screens. To keep things nice and clean we’ll put all the routing in a separate file called router.dart under the UI folder his function receives RouteSettings which contains the name of the route being requested. We’ll also return an error view for any undefined route.

class Router {

static Route<dynamic> generateRoute(RouteSettings settings) {

switch (settings.name) {

case '/' :

return MaterialPageRoute(

builder: (_)=> HomeView()

);

case '/addProduct' :

return MaterialPageRoute(

builder: (_)=> AddProduct()

) ;

case '/productDetails' :

return MaterialPageRoute(

builder: (_)=> ProductDetails()

) ;

default:

return MaterialPageRoute(

builder: (_) => Scaffold(

body: Center(

child: Text('No route defined for ${settings.name}'),

),

));

}

}

}

now under the main file, we will provide the onGenerateRoute property with the static generateRoute function from the Router. You can remove the home property and set the initialRoute to '/' instead. and we will initialize our Provider parameters in the MultiProvider and setup our locator.

void main() {

setupLocator();

runApp(MyApp());

}



class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MultiProvider(

providers: [

ChangeNotifierProvider(builder: (_) => locator<CRUDModel>()),

],

child: MaterialApp(

debugShowCheckedModeBanner: false,

initialRoute: '/',

title: 'Product App',

theme: ThemeData(),

onGenerateRoute: Router.generateRoute,

),

);

}

}

👉Step 5: Creating Home Screen

In HomeView.dart we will fetch different products from the collection products and display them in cards. Whenever the list of products is edited or a new product is added the stream will fetch it from Firebase with the help of our provider CRUDMode after calling it, we will store it into local list variables List<Product> products and then display it.

class _HomeViewState extends State<HomeView> {

List<Product> products;



@override

Widget build(BuildContext context) {

final productProvider = Provider.of<CRUDModel>(context);



return Scaffold(

floatingActionButton: FloatingActionButton(

onPressed: () {

Navigator.pushNamed(context, '/addProduct');

},

child: Icon(Icons.add),

),

appBar: AppBar(

title: Center(child: Text('Home')),

),

body: Container(

child: StreamBuilder(

stream: productProvider.fetchProductsAsStream(),

builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {

if (snapshot.hasData) {

products = snapshot.data.documents

.map((doc) => Product.fromMap(doc.data, doc.documentID))

.toList();

return ListView.builder(

itemCount: products.length,

itemBuilder: (buildContext, index) =>

ProductCard(productDetails: products[index]),

);

} else {

return Text('fetching');

}

}),

),

);

;

}

}

Now we will create ProductCard() to display the details of the product:

import 'package:flutter/material.dart';

import 'package:productapp/core/models/productModel.dart';

import 'package:productapp/ui/views/productDetails.dart';





class ProductCard extends StatelessWidget {

final Product productDetails;



ProductCard({@required this.productDetails});



@override

Widget build(BuildContext context) {

return GestureDetector(

onTap: (){

Navigator.push(context, MaterialPageRoute(builder: (_) => ProductDetails(product: productDetails)));

},

child: Padding(

padding: EdgeInsets.all(8),

child: Card(

elevation: 5,

child: Container(

height: MediaQuery

.of(context)

.size

.height * 0.45,

width: MediaQuery

.of(context)

.size

.width * 0.9,

child: Column(

children: <Widget>[

Hero(

tag: productDetails.id,

child: Image.asset(

'assets/${productDetails.img}.jpg',

height: MediaQuery

.of(context)

.size

.height *

0.35,

),

),

Padding(

padding: EdgeInsets.all(16),

child: Row(

mainAxisAlignment:

MainAxisAlignment.spaceBetween,

children: <Widget>[

Text(

productDetails.name,

style: TextStyle(

fontWeight: FontWeight.w900,

fontSize: 22,

fontStyle: FontStyle.italic),

),

Text(

'${productDetails.price} \$',

style: TextStyle(

fontWeight: FontWeight.w900,

fontSize: 22,

fontStyle: FontStyle.italic,

color: Colors.orangeAccent),

),

],

),

)

],

),

),

),

),

);

}

}

👉Step 5: Creating Product details and add product Screens

to create a new Product item, we will take the name input and price by the user . We instantiate a new Product object using our Finally we upload to Firebase using our addProduct() function called using the CRUD provider

Scaffold(

appBar: AppBar(),

body: Padding(

padding: EdgeInsets.all(12),

child: Form(

key: _formKey,

child: Column(

children: <Widget>[

TextFormField(

decoration: InputDecoration(

border: InputBorder.none,

hintText: 'Product Title',

fillColor: Colors.grey[300],

filled: true,

),

validator: (value) {

if (value.isEmpty) {

return 'Please enter Product Title';

}

},

onSaved: (value) => title = value

),

SizedBox(height: 16,),

TextFormField(

keyboardType: TextInputType.numberWithOptions(),

decoration: InputDecoration(

border: InputBorder.none,

hintText: 'Price',

fillColor: Colors.grey[300],

filled: true,

),

validator: (value) {

if (value.isEmpty) {

return 'Please enter The price';

}

},

onSaved: (value) => price = value

),

DropdownButton<String>(

value: productType,

onChanged: (String newValue) {

setState(() {

productType = newValue;

});

},

items: <String>['Bag', 'Computer', 'Dress', 'Phone','Shoes']

.map<DropdownMenuItem<String>>((String value) {

return DropdownMenuItem<String>(

value: value,

child: Text(value),

);

}).toList(),

),

RaisedButton(

splashColor: Colors.red,

onPressed: () async{

if (_formKey.currentState.validate()) {

_formKey.currentState.save();

await productProvider.addProduct(Product(name: title,price: price,img: productType.toLowerCase()));

Navigator.pop(context) ;

}

},

child: Text('add Product', style: TextStyle(color: Colors.white)),

color: Colors.blue,

)



],

),

),

),

);

Now to delete or update a product we will add another screen to check for the product details and from there we can either delete it or change it

class ProductDetails extends StatelessWidget {

final Product product;



ProductDetails({@required this.product});



@override

Widget build(BuildContext context) {

final productProvider = Provider.of<CRUDModel>(context);



return Scaffold(

appBar: AppBar(

title: Text('Product Details'),

actions: <Widget>[



IconButton(

iconSize: 35,

icon: Icon(Icons.delete_forever),

onPressed: ()async {

await productProvider.removeProduct(product.id);

Navigator.pop(context) ;

},

),

IconButton(

iconSize: 35,

icon: Icon(Icons.edit),

onPressed: (){

Navigator.push(context, MaterialPageRoute(builder: (_)=> ModifyProduct(product: product,)));

},

)

],

),

body: Column(

mainAxisSize: MainAxisSize.max,

crossAxisAlignment: CrossAxisAlignment.center,

children: <Widget>[

Hero(

tag: product.id,

child: Image.asset(

'assets/${product.img}.jpg',

height: MediaQuery.of(context).size.height * 0.35,

width: MediaQuery.of(context).size.width,

),

),

SizedBox(

height: 20,

),

Text(

product.name,

style: TextStyle(

fontWeight: FontWeight.w900,

fontSize: 22,

fontStyle: FontStyle.italic),

),

Text(

'${product.price} \$',

style: TextStyle(

fontWeight: FontWeight.w900,

fontSize: 22,

fontStyle: FontStyle.italic,

color: Colors.orangeAccent),

)

],

),

);

}

}

That’s all our app Should Be ready to rock .