We are building Burger Builder App, where the user can Add ingredients of their own choice and order a Burger.
The idea of this app , came from a similar app - The Burger Builder! created by Maximilian Schwarzmüller → credit https://pro.academind.com/p/react-the-complete-guide-incl-hooks-react-router-redux
Before the workshop, it is most important to be prepared. If not then it will be difficult to follow along.
Please make sure you have prepared yourself:
Learn about Flutter: https://flutter.dev/
Some Vs code extensions i will be using through out this workshop, here is link to the Favourite i use.
Either you can download the starter code from Github ⇒ https://github.com/sumithpdd/BurgerBuilder.git , Fork the repository
or command prompt ⇒ run flutter create burger_builder ,
**$ cd burger_builder $ flutter run cd .\lib\ md helpers md models md providers md screens md services md widgets cd .. code .**
If you got multiple emulators started then you can run your project on all with this command Flutter run -d all
if missing dependecies
flutter pub get
Once you download the starter code for the project you will see some files we have here 📁lib folder
import 'package:flutter/material.dart'; class AppConstants { static const String APP_PRIMARY_COLOR = "#703B09"; static const String APP_BACKGROUND_COLOR = "#F6F8F9"; static const String INGREDIENT_BREAD_TOP_COLOR = "#e27b36"; static const String INGREDIENT_BREAD_BOTTOM_COLOR = "#F08E4A"; static const String INGREDIENT_MEAT_COLOR = "#7f3608"; static const String INGREDIENT_CHEESE_COLOR = "#f4d004"; static const String INGREDIENT_BACON_COLOR = "#bf3813"; static const String INGREDIENT_SALAD_COLOR = "#228c1d"; static const String INGREDIENT_SEED_COLOR = "#c9c9c9"; static const String BUILD_CONTROLS_CONTAINER_COLOR = "#CF8F2E"; static const String BUTTON_BACKGROUND_COLOR = "#DAD735"; static const String BUTTON_TEXT_COLOR = "#966909"; static const String BUTTON_COLOR = "#8F5E1E"; static const String BUTTON_COLOR_CONTINUE = "#5C9210"; static Color hexToColor(String code) { return Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000); } }
The last thing to note is how we imported the images into our app. If we go into a pubspec.yaml file we can see that if we scroll down to assets all I did was uncomment.
This images line and then replace it with assets/images and that’s how we import all of our images from our assets images directory.
**assets: - assets/images/**
Let’s start by cleaning up our main.dart. ⇒ Delete everything.
We’re going to name the title: ‘Flutter Burger Builder’.
Next we have hid the banner in the top right, all I have to do is type debugShowCheckedModeBanner: false and set to false and then for the theme data we’re going to specify a scaffold primaryColor color AppConstants.hexToColor(AppConstants.APP_PRIMARY_COLOR)
We can remove the MyHomePage and we’re going to replace it with home: Home().
import 'package:burger_builder/helpers/app_constants.dart'; import 'package:flutter/material.dart'; import 'screens/home.dart'; void main() { runApp(MyApp()); } **class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Burger Builder', theme: ThemeData( primaryColor: AppConstants.hexToColor(AppConstants.APP_PRIMARY_COLOR), visualDensity: VisualDensity.adaptivePlatformDensity, ), home: Home(), ); } }**
So let’s look under our lib folder 📁 called screens inside screens. We’re going to make a file 📄 called home.dart. And then type in stf which is going to let me create a stateful widget type in shortcuts for long snippets of code.
And once we had saved we see that the app updates now look in our home screen we can see that it contains an app bar with a title and an burger icon button.Go to our home screen and we’re going to add an app bar property here Text(‘Burger Builder’),
import 'package:flutter/material.dart'; class Home extends StatefulWidget { @override _HomeState createState() => _HomeState(); } class _HomeState extends State<Home> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/images/burger-logo.png', fit: BoxFit.contain, height: 32, ), Container( padding: const EdgeInsets.all(8.0), child: Text("Burger Builder")) ], ), )); } }
In home.dart we add a hamburger menu,
Create a new file📄 app_drawer.dart under widget 📁 folder.
import 'package:flutter/material.dart'; class AppDrawer extends StatefulWidget { @override _AppDrawerState createState() => _AppDrawerState(); } class _AppDrawerState extends State<AppDrawer> { @override Widget build(BuildContext context) { return Drawer( child: Column( children: <Widget>[ Spacer(), ListTile( leading: Image.asset( 'assets/images/burger-logo.png', fit: BoxFit.contain, height: 32, ), title: Text('Burger Builder'), onTap: () {}, ), Divider(), ListTile( leading: Icon(Icons.shopping_cart), title: Text('Checkout'), onTap: () {}, ), Spacer(flex: 8), ], ), ); } }
modify home.dart
import 'package:burger_builder/widgets/app_drawer.dart'; import 'package:flutter/material.dart'; class Home extends StatefulWidget { @override _HomeState createState() => _HomeState(); } class _HomeState extends State<Home> { **GlobalKey<ScaffoldState> _drawerKey = GlobalKey();** @override Widget build(BuildContext context) { return Scaffold( **key: _drawerKey, // assign key to Scaffold** appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/images/burger-logo.png', fit: BoxFit.contain, height: 32, ), Container( padding: const EdgeInsets.all(8.0), child: Text("Burger Builder")) ], ), **leading: IconButton( icon: Icon(Icons.menu), iconSize: 30.0, color: Colors.white, onPressed: () => _drawerKey.currentState.openDrawer(), ), elevation: 0.0, actions: <Widget>[ IconButton( icon: Icon(Icons.logout), onPressed: () {}, ), ], ), drawer: AppDrawer(),** backgroundColor: Colors.white, **body: Container(),** ); } }
We need some dummy data ,Create a new file📄 ingredients_model.dart under model 📁 folder.
class IngredientsModel { String name; String label; double price; IngredientsModel({this.name, this.label, this.price}); }
create another file📄 ingredients_model.dart under model 📁 folder.
import 'package:burger_builder/models/ingredients_model.dart'; List<IngredientsModel> dummyData = [ IngredientsModel(name: 'bacon', label: 'Bacon', price: 0.7), IngredientsModel(name: 'cheese', label: 'Cheese', price: 0.4), IngredientsModel(name: 'meat', label: 'Meat', price: 1.3), IngredientsModel(name: 'salad', label: 'Salad', price: 0.5), ];
Back in dummy_data.dart are we can see how we instantiate data. Here we create all the different types of ingredients each with our own name , label and price.
create another file📄 burger.dart under screen 📁 folder. Create a stateful widget Burger
import 'package:flutter/material.dart'; class Burger extends StatefulWidget { const Burger({Key key}) : super(key: key); @override _BurgerState createState() => _BurgerState(); } class _BurgerState extends State<Burger> { @override Widget build(BuildContext context) { **return Container( child: Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: ListView( children: [ Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text("bread-top"), Text("Ingredients"), Text("bread-bottom"), ], ), ], ), ), ),** ); } }
Update home.dart , add reference to the
**body: Column(children: <Widget>[ Burger(), Text("Build Controls"), ]),**
Save and Reload the app.
Looking at the design we need a top Bun, ingredients and bottom Bun, Area with controls. Controls is where the user selects the ingredients he wants to order. we will create another model to store user selected ingredients and order . Create another file📄 user_order_model.dart under model 📁 folder.
import 'package:burger_builder/models/ingredients_model.dart'; class UserOrderModel { final String customer; final List<UserSelectedIngredientModel> userIngredients; double totalPrice; UserOrderModel({this.customer, this.userIngredients, this.totalPrice}); } class UserSelectedIngredientModel { IngredientsModel ingredient; int count; UserSelectedIngredientModel({this.ingredient, this.count}); }
We will create a widget file📄 burger_ingredient.dart under widgets 📁 folder. create a StatefulWidget ⇒ BurgerIngredient .
import 'package:burger_builder/helpers/app_constants.dart'; import 'package:flutter/material.dart'; import 'dart:math' as math; class **BurgerIngredient** extends StatefulWidget { final String type; const BurgerIngredient({Key key, this.type}) : super(key: key); @override _BurgerIngredientState createState() => _BurgerIngredientState(); } class _BurgerIngredientState extends State<BurgerIngredient> { @override Widget build(BuildContext context) { const double BURGER_WIDTH = 325; var ingredient; switch (widget.type) { case ('bread-top'): ingredient = Padding( padding: const EdgeInsets.only(bottom: 5.0), child: Stack( children: [ Container( height: 60, width: BURGER_WIDTH, decoration: BoxDecoration( color: AppConstants.hexToColor( AppConstants.INGREDIENT_BREAD_TOP_COLOR), borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#C15711"), AppConstants.hexToColor("#C15711") ]), boxShadow: [ BoxShadow( color: AppConstants.hexToColor("#c15711"), offset: Offset(0, 3), // changes position of shadow ), ], ), child: Padding( padding: const EdgeInsets.all(10.0), child: SeedsWidget(), ), ), Container( height: 60, width: 315, decoration: BoxDecoration( color: AppConstants.hexToColor("#e27b36"), borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#e27b36"), AppConstants.hexToColor("#bc581e"), ]), boxShadow: [ BoxShadow( color: AppConstants.hexToColor("#c15711"), offset: Offset(0, 3), // changes position of shadow ), ], ), child: Padding( padding: const EdgeInsets.all(10.0), child: SeedsWidget(), ), ), ], ), ); break; case ('bread-bottom'): ingredient = Padding( padding: const EdgeInsets.only(top: 5.0), child: Stack( children: [ Container( height: 50, width: BURGER_WIDTH, decoration: BoxDecoration( color: AppConstants.hexToColor("#c15711"), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), boxShadow: [ BoxShadow( color: AppConstants.hexToColor("#c15711"), offset: Offset(0, 3), // changes position of shadow ), ], ), ), Container( height: 50, width: 315, decoration: BoxDecoration( color: AppConstants.hexToColor("#e27b36"), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#F08E4A"), AppConstants.hexToColor("#e27b36") ]), boxShadow: [ BoxShadow( color: AppConstants.hexToColor("#e27b36"), offset: Offset(0, 3), // changes position of shadow ), ], )), ], ), ); break; case ('meat'): ingredient = Padding( padding: const EdgeInsets.all(5.0), child: Container( width: BURGER_WIDTH, height: 30, decoration: BoxDecoration( color: AppConstants.hexToColor(AppConstants.INGREDIENT_MEAT_COLOR), borderRadius: BorderRadius.all( Radius.circular(10), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#7f3608"), AppConstants.hexToColor("#702e05") ]), )), ); break; case ('cheese'): ingredient = Padding( padding: const EdgeInsets.all(5.0), child: Container( height: 15, width: 355, decoration: BoxDecoration( color: AppConstants.hexToColor(AppConstants.INGREDIENT_CHEESE_COLOR), borderRadius: BorderRadius.all( Radius.circular(10), ), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#f4d004"), AppConstants.hexToColor("#d6bb22") ]), ), ), ); break; case ('bacon'): ingredient = Padding( padding: const EdgeInsets.all(5.0), child: Container( height: 15, width: BURGER_WIDTH, decoration: BoxDecoration( color: AppConstants.hexToColor(AppConstants.INGREDIENT_BACON_COLOR), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#bf3813"), AppConstants.hexToColor("#c45e38") ]), ), ), ); break; case ('salad'): ingredient = Padding( padding: const EdgeInsets.all(5.0), child: Container( height: 20, width: 340, decoration: BoxDecoration( color: AppConstants.hexToColor(AppConstants.INGREDIENT_SALAD_COLOR), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: [ 0.1, 0.9 ], colors: [ AppConstants.hexToColor("#228c1d"), AppConstants.hexToColor("#91ce50") ]), borderRadius: BorderRadius.only( topLeft: Radius.circular(10), bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10), topRight: Radius.circular(10), ), ), ), ); break; default: ingredient = null; } return ingredient; } } class SeedsWidget extends StatelessWidget { const SeedsWidget({ Key key, }) : super(key: key); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ Transform.rotate( angle: -math.pi / 3, child: Container( height: 15, width: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Colors.white, boxShadow: [ BoxShadow( color: AppConstants.hexToColor( AppConstants.INGREDIENT_SEED_COLOR), blurRadius: 2, offset: Offset(0, 3), // changes position of shadow ), ]), ), ), Transform.rotate( angle: 45, child: Container( height: 15, width: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Colors.white, boxShadow: [ BoxShadow( color: AppConstants.hexToColor( AppConstants.INGREDIENT_SEED_COLOR), blurRadius: 2, offset: Offset(0, 3), // changes position of shadow ), ]), ), ), Transform.rotate( angle: 5, child: Container( height: 15, width: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Colors.white, boxShadow: [ BoxShadow( color: AppConstants.hexToColor( AppConstants.INGREDIENT_SEED_COLOR), blurRadius: 2, offset: Offset(0, 3), // changes position of shadow ), ]), ), ), Transform.rotate( angle: -math.pi / 5, child: Container( height: 15, width: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Colors.white, boxShadow: [ BoxShadow( color: AppConstants.hexToColor( AppConstants.INGREDIENT_SEED_COLOR), blurRadius: 2, offset: Offset(0, 3), // changes position of shadow ), ]), ), ), Transform.rotate( angle: math.pi / 8, child: Container( height: 15, width: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Colors.white, boxShadow: [ BoxShadow( color: AppConstants.hexToColor( AppConstants.INGREDIENT_SEED_COLOR), blurRadius: 2, offset: Offset(0, 3), // changes position of shadow ), ]), ), ), ], ); } }
Modify file📄 burger.dart , lets add the new widget.
Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ **BurgerIngredient(type: "bread-top"),** Text("Ingredients"), **BurgerIngredient(type: "bread-bottom"),** ], ),
Try different ingredients. hint..
BurgerIngredient(type: "bread-top"), BurgerIngredient(type: "bacon"), BurgerIngredient(type: "cheese"), BurgerIngredient(type: "meat"), BurgerIngredient(type: "salad"), BurgerIngredient(type: "bread-bottom"),
Lets Make this dynamic, Modify file📄 burger.dart , add ingredientsList , create a getter transformedIngredients and add the dymamic ingredients to a ListView
**List<Widget> ingredientsList = [];** BurgerIngredient(type: "bread-top"), **new ListView( shrinkWrap: true, children: transformedIngredients, ),** BurgerIngredient(type: "bread-bottom"), ..... **get transformedIngredients { for (var ingredient in dummyData) { ingredientsList.add(BurgerIngredient(type: ingredient.name)); } return ingredientsList; }**
Add some padding to our text , right click → add padding set padding: const EdgeInsets.all(20.0), Now our text’s are space nicely in order
burger. dart
import 'package:burger_builder/models/dummy_data.dart'; import 'package:burger_builder/widgets/burger_ingredient.dart'; import 'package:flutter/material.dart'; class Burger extends StatefulWidget { Burger({Key key}) : super(key: key); @override _BurgerState createState() => _BurgerState(); } class _BurgerState extends State<Burger> { List<Widget> ingredientsList = new List<Widget>(); @override Widget build(BuildContext context) { return Container( child: Expanded( child: Center( child: Padding( padding: const EdgeInsets.all(8.0), child: ListView( children: [ BurgerIngredient(type: "bread-top"), new ListView( shrinkWrap: true, children: transformedIngredients, ), BurgerIngredient(type: "bread-bottom"), ], ), ), ), ), ); } get transformedIngredients { for (var ingredient in dummyData) { ingredientsList.add(BurgerIngredient(type: ingredient.name)); } return ingredientsList; } }
Now we will create controls , so that we can add and remove ingredients
Create a new file📄 build_controls.dart under widget 📁 , we will add the Price, controls and Order now Button.
import 'package:burger_builder/helpers/app_constants.dart'; import 'package:burger_builder/models/ingredients_model.dart'; import 'package:burger_builder/models/user_order_model.dart'; import 'package:flutter/material.dart'; class BuildControls extends StatefulWidget { BuildControls( {Key key, this.userOrderModel, this.addHandler, this.removeHandler, this.ingredients}) : super(key: key); final UserOrderModel userOrderModel; final Function addHandler; final Function removeHandler; final List<IngredientsModel> ingredients; @override _BuildControlsState createState() => _BuildControlsState(); } class _BuildControlsState extends State<BuildControls> { @override Widget build(BuildContext context) { final totalPrice = widget.userOrderModel.totalPrice; return Container( color: AppConstants.hexToColor(AppConstants.BUILD_CONTROLS_CONTAINER_COLOR), child: Column( children: [ Padding( padding: const EdgeInsets.only(top: 8.0, left: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( 'Current Price:', style: TextStyle( color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.bold, ), ), SizedBox(width: 10), Text( '\$${totalPrice.toStringAsFixed(2)}', style: TextStyle( color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.bold, ), ), ], ), ), buttonBar(), Align( alignment: Alignment.bottomCenter, child: RaisedButton( onPressed: () {}, child: const Text('ORDER NOW', style: TextStyle(fontSize: 20)), color: AppConstants.hexToColor(AppConstants.BUTTON_BACKGROUND_COLOR), textColor: AppConstants.hexToColor(AppConstants.BUTTON_TEXT_COLOR), elevation: 5, ), ) ], ), ); } Widget buttonBar() { return Column( children: widget.ingredients.map<Widget>((ingredient) { final userIngredient = widget.userOrderModel.userIngredients .singleWhere((ing) => ing.ingredient.name == ingredient.name, orElse: () => null); return Text("ingredients selection = ${ingredient.label}"); }).toList(), ); } }
We use string interpolation ${ingredient.label} tp add info about the ingredient info.
update file📄 home.dart , we will create data from our dummy user model. and update the body to use the build controls.
**UserOrderModel userOrderModel = UserOrderModel( customer: "sumith", userIngredients: List<UserSelectedIngredientModel>(), totalPrice: 0, );** .......... ............. body: Column(children: <Widget>[ Burger(), **BuildControls( userOrderModel: userOrderModel, addHandler: () {}, removeHandler: () {}, ingredients: dummyData), ]),**
Now we will create controls for the ingredients Create a new file📄 build_control.dart under widget 📁
import 'package:burger_builder/models/ingredients_model.dart'; import 'package:flutter/material.dart'; import 'custom_stepper.dart'; class BuildControl extends StatefulWidget { BuildControl( {Key key, @required this.ingredient, @required this.currentValue, @required this.addHandler, @required this.removeHandler}) : super(key: key); final IngredientsModel ingredient; final int currentValue; final Function addHandler; final Function removeHandler; @override _BuildControlState createState() => _BuildControlState(); } class _BuildControlState extends State<BuildControl> { @override Widget build(BuildContext context) { return Container( child: Column( children: [ Padding( padding: const EdgeInsets.only(left: 8.0), child: Row( children: [ Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.baseline, children: [ Text( widget.ingredient.label, style: TextStyle( color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.bold, ), ), SizedBox(width: 10), Text( "(\$" + "${widget.ingredient.price.toStringAsFixed(2)})", style: TextStyle( color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.bold, ), ), ], ), Spacer(), **CustomStepper( value: widget.currentValue, upperLimit: 5, lowerLimit: 0, stepValue: 1, iconSize: 25, name: widget.ingredient.name, addHandler: widget.addHandler, removeHandler: widget.removeHandler, ) ],** ), ), ], ), ); } }
We are using a CustomStepper widget and RoundedIconButtom Create a new file📄 custom_stepper.dart under widget 📁
import 'package:flutter/material.dart'; import 'rounded_icon_button.dart'; class CustomStepper extends StatefulWidget { CustomStepper({ @required this.lowerLimit, @required this.upperLimit, @required this.stepValue, @required this.iconSize, @required this.value, @required this.name, @required this.addHandler, @required this.removeHandler, }); final int lowerLimit; final int upperLimit; final int stepValue; final double iconSize; final int value; final String name; final Function addHandler; final Function removeHandler; @override _CustomStepperState createState() => _CustomStepperState(); } class _CustomStepperState extends State<CustomStepper> { int value; @override void initState() { value = widget.value; super.initState(); } @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ RoundedIconButton( icon: Icons.remove, iconSize: widget.iconSize, onPress: widget.value == widget.lowerLimit ? null : () { _setValue(); widget.removeHandler(widget.name); }, ), Container( width: widget.iconSize, child: Text( '${widget.value}', style: TextStyle( fontSize: widget.iconSize * 0.8, ), textAlign: TextAlign.center, ), ), RoundedIconButton( icon: Icons.add, iconSize: widget.iconSize, onPress: widget.value == widget.upperLimit ? null : () { _setValue(); widget.addHandler(widget.name); }, ), ], ); } _setValue() => setState(() { value = widget.value == widget.upperLimit ? widget.upperLimit : value += widget.stepValue; }); }
Create a new file📄 rounded_icon_button.dart under widget 📁
import 'package:burger_builder/helpers/app_constants.dart'; import 'package:flutter/material.dart'; class RoundedIconButton extends StatelessWidget { RoundedIconButton( {@required this.icon, @required this.onPress, @required this.iconSize}); final IconData icon; final Function onPress; final double iconSize; @override Widget build(BuildContext context) { return RawMaterialButton( constraints: BoxConstraints.tightFor(width: iconSize, height: iconSize), elevation: 6.0, onPressed: onPress, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(iconSize * 0.2)), fillColor: AppConstants.hexToColor(AppConstants.BUTTON_COLOR), child: Icon( icon, color: Colors.white, size: iconSize * 0.8, ), ); } }
modify ⇒ build_controls.dart also we make sure OrderNow is disabled if there is no item in the cart onPressed: totalPrice <= 0 ? null : () {},
........ **onPressed: totalPrice <= 0 ? null : () {},** ......... Widget buttonBar() { return Column( children: widget.ingredients.map<Widget>((ingredient) { final userIngredient = widget.userOrderModel.userIngredients .singleWhere((ing) => ing.ingredient.name == ingredient.name, orElse: () => null); **final currentCount = userIngredient?.count ?? 0; return BuildControl( ingredient: ingredient, currentValue: currentCount, addHandler: widget.addHandler, removeHandler: widget.removeHandler, ); }).toList(),** ); }
Click wont work, when you click on + -
Now the skeleton of the app is done, lets Add functionality make it working.
Lets give it some state, we will do it the hard way, First we add some functions to add remove ingredients. In home.dart add private function with _ ⇒ _addIngredientHandler and _removeIngredientHandler here we talk about setState .
body: Column(children: <Widget>[ ***Burger( userOrderModel: userOrderModel, ),*** BuildControls( userOrderModel: userOrderModel, addHandler: **_addIngredientHandler**, removeHandler: **_removeIngredientHandler**, ingredients: dummyData), ]), ); } **_addIngredientHandler(String name) { var ingredient = dummyData.singleWhere((ing) => ing.name == name); final foundIngredient = userOrderModel.userIngredients.singleWhere( (element) => element.ingredient.name == name, orElse: () => null, ); if (foundIngredient == null) { setState(() { userOrderModel.userIngredients.add( UserSelectedIngredientModel(ingredient: ingredient, count: 1), ); }); } else { setState(() { foundIngredient.count++; }); } setState(() { userOrderModel.totalPrice = userOrderModel.totalPrice + ingredient.price; }); } _removeIngredientHandler(name) { final ingredient = dummyData.singleWhere((ing) => ing.name == name); final foundIngredient = userOrderModel.userIngredients.singleWhere( (element) => element.ingredient.name == name, orElse: () => null, ); if (foundIngredient != null) { setState(() { foundIngredient.count--; }); } setState(() { userOrderModel.totalPrice = userOrderModel.totalPrice - ingredient.price; userOrderModel.userIngredients .removeWhere((element) => element.count == 0); }); }** }
Modify Burger screen to now react to dynamic addition of ingredients , in burger.dart , refactor and create a widget to show a message if empty ingredients. EmptyIngredients
import 'package:burger_builder/models/user_order_model.dart'; import 'package:burger_builder/widgets/burger_ingredient.dart'; import 'package:flutter/material.dart'; class Burger extends StatefulWidget { **final UserOrderModel userOrderModel;** const Burger({Key key, this.userOrderModel}) : super(key: key); @override _BurgerState createState() => _BurgerState(); } class _BurgerState extends State<Burger> { @override Widget build(BuildContext context) { final userIngredients = widget.userOrderModel.userIngredients; final emptyIngredients = userIngredients == null || userIngredients.length == 0; return Container( child: Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: ListView( children: [ Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ BurgerIngredient(type: "bread-top"), ***if (emptyIngredients) EmptyIngredients(), ...transformedIngredients,*** BurgerIngredient(type: "bread-bottom"), ], ), ], ), ), ), ); } **get transformedIngredients { final userIngredients = widget.userOrderModel.userIngredients; List<Widget> ingredientsList = []; for (var selectedIngredient in userIngredients) { for (var i = 0; i < selectedIngredient.count; i++) { ingredientsList.add( BurgerIngredient(type: selectedIngredient.ingredient.name), ); } } return ingredientsList; } } class EmptyIngredients extends StatelessWidget { const EmptyIngredients({ Key key, }) : super(key: key); @override Widget build(BuildContext context) { return Container( child: Center( child: Text( "Please start adding ingredients!", style: TextStyle( color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.bold, ), ), ), ); } }**
Now add Order Summary screen⇒ Create a new file📄 order_summary.dart under screen 📁
import 'package:burger_builder/helpers/app_constants.dart'; import 'package:burger_builder/models/user_order_model.dart'; import 'package:flutter/material.dart'; class OrderSummary extends StatefulWidget { OrderSummary({Key key, @required this.userOrderModel}) : super(key: key); UserOrderModel userOrderModel; @override _OrderSummaryState createState() => _OrderSummaryState(); } class _OrderSummaryState extends State<OrderSummary> { bool visible = false; @override Widget build(BuildContext context) { return Container( color: Color(0xff757575), child: Container( padding: EdgeInsets.all(20.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(25.0), topRight: Radius.circular(25.0))), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text( 'Your Order', textAlign: TextAlign.center, style: TextStyle( fontSize: 20.0, color: Colors.black, fontWeight: FontWeight.bold), ), SizedBox(height: 5.0), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'A delicious burger with the following ingredients:', style: TextStyle(fontSize: 15.0, color: Colors.black), ), ], ), Expanded( child: ListView.separated( itemCount: widget.userOrderModel.userIngredients.length, separatorBuilder: (_, __) => Divider(height: 0.5), itemBuilder: (BuildContext context, int index) { var userIngredient = widget.userOrderModel.userIngredients[index]; return ListTile( leading: Icon( Icons.check_circle_outline_outlined, color: AppConstants.hexToColor( AppConstants.APP_PRIMARY_COLOR), ), title: Text( '${userIngredient.ingredient.label} (${userIngredient.ingredient.price.toStringAsFixed(2)}) X ${userIngredient.count}', style: TextStyle(fontSize: 15.0, color: Colors.black), ), trailing: Text( '${(userIngredient.ingredient.price * userIngredient.count).toStringAsFixed(2)} ', style: TextStyle(fontSize: 15.0, color: Colors.black), ), ); }, ), ), Text( 'Total Price : \$' + "${widget.userOrderModel.totalPrice.toStringAsFixed(2)}", style: TextStyle( fontSize: 15.0, color: AppConstants.hexToColor( AppConstants.BUTTON_COLOR_CONTINUE, ), fontWeight: FontWeight.bold), ), Text( 'Continue to Chekout?', style: TextStyle(fontSize: 15.0, color: Colors.black), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ FlatButton( child: Text( 'CANCEL', style: TextStyle(color: Colors.white), ), color: AppConstants.hexToColor(AppConstants.BUTTON_COLOR), onPressed: () => Navigator.pop(context), ), visible ? CircularProgressIndicator( backgroundColor: AppConstants.hexToColor( AppConstants.APP_PRIMARY_COLOR, ), ) : FlatButton( child: Text( 'CONTINUE', style: TextStyle(color: Colors.white), ), color: AppConstants.hexToColor( AppConstants.BUTTON_COLOR_CONTINUE, ), onPressed: () {}), ], ), ], ), ), ); } }
Update file📄 build_controls.dart call Modal Bottom sheet
........... buttonBar(), Align( alignment: Alignment.bottomCenter, child: RaisedButton( ***onPressed: totalPrice <= 0 ? null : () { showModalBottomSheet( context: context, builder: (context) => Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: OrderSummary( userOrderModel: widget.userOrderModel, ), ), ); },*** child: const Text('ORDER NOW', style: TextStyle(fontSize: 20)), color: .............
open https://firebase.google.com/ go to console and add a new project , name it BurgerBuilder
We will use realtime database
To connect to it we will use http, add http to pubspec.xml or use pubassist
if You get an error, use version http: ^0.12.2
http >=0.13.0-nullsafety.0 which requires SDK version >=2.12.0-0 <3.0.0,
Create a new file📄 http_service.dart under services 📁 Lets get the ingredients from firebase
import 'dart:convert'; import 'package:burger_builder/models/dummy_data.dart'; import 'package:burger_builder/models/ingredients_model.dart'; import 'package:http/http.dart' as http; class HttpService { static const Url = 'https://todo-flutter-8a80f.firebaseio.com'; Future<List<IngredientsModel>> sendData() async { var body = json.encode(List<dynamic>.from(dummyData.map((x) => x.toJson()))); final response = await http.put("$Url/burgeringredients.json", body: body); final parsed = jsonDecode(response.body).cast<Map<String, dynamic>>(); return new List<IngredientsModel>(); } Future<List<IngredientsModel>> fetchTheIngredients() async { final response = await http.get("$Url/burgeringredients.json"); final parsed = jsonDecode(response.body).cast<Map<String, dynamic>>(); return parsed .map<IngredientsModel>((json) => IngredientsModel.fromJson(json)) .toList(); } }
we will get ingredients from the database Update ingredients_model.dart
class IngredientsModel { String name; String label; double price; IngredientsModel({this.name, this.label, this.price}); factory IngredientsModel.fromJson(Map<String, dynamic> json) => IngredientsModel( name: json['name'] as String, label: json['label'] as String, price: json['price'] as double); Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['name'] = this.name; data['label'] = this.label; data['price'] = this.price; return data; } }
Update home.dart we will use FutureBuilder , update body, and remove dummy data references.
List<IngredientsModel> ingredients = []; ....... body**: FutureBuilder<List<IngredientsModel>>( future: HttpService().fetchTheIngredients(), builder: (context, snapshot) { if (snapshot.hasError) print(snapshot.error); return snapshot.hasData ? mainView(snapshot.data) : Center(child: CircularProgressIndicator()); }, ), ); } Column mainView(data) { ingredients = data; return Column(children: <Widget>[ Burger( userOrderModel: userOrderModel, ), BuildControls( userOrderModel: userOrderModel, addHandler: _addIngredientHandler, removeHandler: _removeIngredientHandler, ingredients: ingredients) ]); } ..........** _addIngredientHandler(String name) { **var ingredient = ingredients.singleWhere((ing) => ing.name == name);**
Now we do the same and send orders to firebase.
update UserOrderModel so that we can encode and decode data. ⇒ file📄 user_order_model.dart
import 'package:burger_builder/models/ingredients_model.dart'; class UserOrderModel { final String customer; final List<UserSelectedIngredientModel> userIngredients; double totalPrice; UserOrderModel({this.customer, this.userIngredients, this.totalPrice}); **factory UserOrderModel.fromJson(Map<String, dynamic> json) => UserOrderModel( customer: json['customer'] as String, userIngredients: json['userIngredients'].forEach((v) { return (new UserSelectedIngredientModel.fromJson(v)); }), totalPrice: json['totalPrice'] as double, ); Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['customer'] = this.customer; data['userIngredients'] = List<dynamic>.from(userIngredients.map((x) => x.toJson())); data['totalPrice'] = this.totalPrice.toStringAsFixed(2); return data; }** } class UserSelectedIngredientModel { IngredientsModel ingredient; int count; UserSelectedIngredientModel({this.ingredient, this.count}); **factory UserSelectedIngredientModel.fromJson(Map<String, dynamic> json) => UserSelectedIngredientModel( ingredient: IngredientsModel.fromJson(json['ingredient']), count: json['count'] as int); Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['ingredient'] = this.ingredient.toJson(); data['count'] = this.count; return data; }** }
Add a call to post data ⇒ file📄 http_service.dart
.............. //post Order **Future<String> purchaseContinue(UserOrderModel userOrderModel) async { var body = json.encode(userOrderModel); final response = await http.post("$Url/orders.json", body: body); if (response.statusCode == 200) { // If the server did return a 200 CREATED response, // then parse the JSON. print(response.body); return response.body; } else { // If the server did not return a 201 CREATED response, // then throw an exception. throw Exception('Failed to create '); } }**
update Ordersummary ⇒ file📄 order_summary.dart
.................... **onPressed: () async { setState(() { visible = true; }); var orderid = await HttpService() .purchaseContinue(widget.userOrderModel); if (orderid.length > 0) { setState(() { widget.userOrderModel = new UserOrderModel( customer: "Sumith", userIngredients: new List<UserSelectedIngredientModel>(), totalPrice: 0.00); }); SnackBar( behavior: SnackBarBehavior.floating, content: Text('order placed - ' + orderid), ); } setState(() { visible = false; }); Navigator.pop(context); }, ),**
[State Management with Provider](BurgerBuilder%20Flutter%20Workshop%2076acbc117f32445ab83112dc2c1392d5/State%20Management%20with%20Provider%2063997bbb648d47129bcb2c194ec17664.md
Quick Links
Legal Stuff