HomeAbout MeContact

BurgerBuilder Flutter Workshop Part 1

By Sumith Parambat Damodaran
Published in Technology
February 25, 2021
4 min read
BurgerBuilder Flutter Workshop Part 1

BurgerBuilder Flutter Workshop

Overview

Introduction

We are building Burger Builder App, where the user can Add ingredients of their own choice and order a Burger.

1_ReactvsFlutter_Flutter
1_ReactvsFlutter_Flutter

1_ReactvsFlutter_React
1_ReactvsFlutter_React

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

*VERY IMPORTANT**

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/

  • Have a Google account → this is needed to create a Firebase project.
  • Have Flutter installed → https://flutter.dev/docs/get-started/install
  • Make sure to test that you can run a Flutter project on either an emulator or a physical phone. → https://flutter.dev/docs/get-started/test-drive?tab=androidstudio
  • Have VS Code installed https://code.visualstudio.com/.
  • Have git installed on your computer → https://git-scm.com/book/en/v2/Getting-Started-Installing-Git

vscode Extension

Some Vs code extensions i will be using through out this workshop, here is link to the Favourite i use.

Flutter VSCode extensions

Project Structure

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

  • 📄main.dart - that’s is the start of the app
  • **📁helper folder 📄app_constants.dart - that’s is where we define all the constants and styles. We would like to keep our styles to a common location under helpers 📁 → file**📄app_constants.dart review the code .
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);
  }
}

AppBuilder
AppBuilder

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/**

Home screen and appbar

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.

  • Name it Home after that.
  • Import material by hovering over state widget. On the Mac you can hit command period and then go down to material and hit enter.
  • Replace the container here with a scaffold for now.
  • Go back to main that are hover over home screen a quick fix and then we can import the home.dart.

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"))
        ],
      ),
    ));
  }
}

Appbar and Title
Appbar and Title

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(),**
    );
  }
}

AppDrawer
AppDrawer

We need some dummy data ,Create a new file📄 ingredients_model.dart under model 📁 folder.

  • create a class IngredientsModel
class IngredientsModel {
  String name;
  String label;
  double price;
  IngredientsModel({this.name, this.label, this.price});   
}

create another file📄 ingredients_model.dart under model 📁 folder.

  • 📄dummy_data.dart - which holds all their data in there we have four different ingredients in the app.
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 a Burger Structure

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"),**
                ],
              ),

Burger with empty ingredients
Burger with empty ingredients

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;
  }
}

Build Controls for user selection

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),
      ]),**

Build Controls
Build Controls

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 + -

Build Controls
Build Controls

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,
          ),
        ),
      ),
    );
  }
}**

Adding Real Ingredients Empty
Adding Real Ingredients Empty

Adding Real Ingredients
Adding Real Ingredients

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:

.............

Connect to firebase

open https://firebase.google.com/ go to console and add a new project , name it BurgerBuilder

Firebase step1
Firebase step1

11_Firebase step 2
11_Firebase step 2

11_Firebase step 3
11_Firebase step 3

We will use realtime database

12_Realtime_Step1
12_Realtime_Step1

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

12_Realtime_Step2
12_Realtime_Step2

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


Tags

#flutter#workshop#Studying
Previous Article
Product Manager vs Project Manger
Sumith Parambat Damodaran

Sumith Parambat Damodaran

Product Manager

Topics

General
Product Management
Technology

Related Posts

Burger Builder Flutter Workshop Part 2 - Provider
February 25, 2021
1 min

Quick Links

Advertise with usAbout UsContact Us

Social Media