HomeAbout MeContact

Burger Builder Flutter Workshop Part 2 - Provider

By Sumith Parambat Damodaran
Published in Technology
February 25, 2021
1 min read
Burger Builder Flutter Workshop Part 2 - Provider

State Management with Provider

Problem:

Can a widget request what they need without constructor injection or using singletons

https://pub.dev/packages/provider

A wrapper around InheritedWidget to make them easier to use and more reusable

Using the Provider

add provider package to our project in pubspec.yaml

provider: ^4.3.3

Created a new class file📄 user_order_provider.dart under providers 📁 folder. We are moving all the logic that effects our usermodel here.

import 'package:burger_builder/models/dummy_data.dart';
import 'package:burger_builder/models/user_order_model.dart';
import 'package:flutter/foundation.dart';

class UserOrderProvider extends ChangeNotifier {
  UserOrderModel _myuserOrderModel =
      UserOrderModel(customer: "sumith", userIngredients: [], totalPrice: 10);

  UserOrderModel get userOrderModel {
    return _myuserOrderModel;
  }

  bool get isEmptyIngredients {
    return _myuserOrderModel.userIngredients == null ||
        _myuserOrderModel.userIngredients.length == 0;
  }

  addIngredientHandler(String name) {
    var ingredient = dummyData.singleWhere((ing) => ing.name == name);

    final foundIngredient = _myuserOrderModel.userIngredients.singleWhere(
      (element) => element.ingredient.name == name,
      orElse: () => null,
    );
    if (foundIngredient == null) {
      _myuserOrderModel.userIngredients.add(
        UserSelectedIngredientModel(ingredient: ingredient, count: 1),
      );
    } else {
      foundIngredient.count++;
    }
    _myuserOrderModel.totalPrice =
        _myuserOrderModel.totalPrice + ingredient.price;
    notifyListeners();
  }

  removeIngredientHandler(String name) {
    final ingredient = dummyData.singleWhere((ing) => ing.name == name);

    final foundIngredient = _myuserOrderModel.userIngredients.singleWhere(
      (element) => element.ingredient.name == name,
      orElse: () => null,
    );
    if (foundIngredient != null) {
      foundIngredient.count--;
    }
    _myuserOrderModel.totalPrice =
        _myuserOrderModel.totalPrice - ingredient.price;
    _myuserOrderModel.userIngredients
        .removeWhere((element) => element.count == 0);
    notifyListeners();
  }

  void setDummyData() {
    _myuserOrderModel =
        UserOrderModel(customer: "sumith", userIngredients: [], totalPrice: 10);
  }
}

Refactor file📄 main.dart ⇒ build method

return **ChangeNotifierProvider<UserOrderProvider>(
      create: (context) => UserOrderProvider(),**
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Burger Builder',
        theme: ThemeData(
          primaryColor: AppConstants.hexToColor(AppConstants.APP_PRIMARY_COLOR),
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: Home(),
      ),
    );

Start Removing dependency on userOrderModel from file📄 burger.dart

import 'package:burger_builder/models/user_order_model.dart';
import 'package:burger_builder/providers/user_order_provider.dart';
import 'package:burger_builder/widgets/burger_ingredient.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.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: [
                  BurgerIngredient(type: "bread-top"),
                  **if (Provider.of<UserOrderProvider>(context, listen: true)
                      .isEmptyIngredients)**
                    EmptyIngredients(),
                  ...transformedIngredients,
                  BurgerIngredient(type: "bread-bottom"),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  get transformedIngredients {
    **final myuserOrderModel =
        Provider.of<UserOrderProvider>(context).userOrderModel;**
    List<Widget> ingredientsList = [];
    for (var selectedIngredient in **myuserOrderModel.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,
          ),
        ),
      ),
    );
  }
}

Remove dependency on userOrderModel from file📄 build_controls.dart

import 'package:burger_builder/helpers/app_constants.dart';
import 'package:burger_builder/models/dummy_data.dart';
import 'package:burger_builder/models/ingredients_model.dart';
import 'package:burger_builder/models/user_order_model.dart';
import 'package:burger_builder/providers/user_order_provider.dart';
import 'package:burger_builder/screens/order_summary.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'build_control.dart';

class BuildControls extends StatefulWidget {
  **BuildControls({Key key, this.ingredients}) : super(key: key);**

  final List<IngredientsModel> ingredients;

  @override
  _BuildControlsState createState() => _BuildControlsState();
}

class _BuildControlsState extends State<BuildControls> {
  @override
  Widget build(BuildContext context) {
    **final totalPrice = Provider.of<UserOrderProvider>(context, listen: true)
        .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: totalPrice <= 0
                  ? null
                  : () {
                      showModalBottomSheet(
                        context: context,
                        builder: (context) => Container(
                          padding: EdgeInsets.only(
                            bottom: MediaQuery.of(context).viewInsets.bottom,
                          ),
                          child: OrderSummary(),
                        ),
                      );
                    },
              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 =**
            **Provider.of<UserOrderProvider>(context, listen: true)
                .userOrderModel
                ?.userIngredients
                .singleWhere((ing) => ing.ingredient.name == ingredient.name,
                    orElse: () => null);**

        final currentCount = userIngredient?.count ?? 0;

        return BuildControl(
          ingredient: ingredient,
          currentValue: currentCount,
        );
      }).toList(),
    );
  }
}

Remove dependency on userOrderModel from file📄 build_control.dart , here we are adding refences to the functions in UserOrderProvider and passing that to custom stepper.

import 'package:burger_builder/models/ingredients_model.dart';
import 'package:burger_builder/models/user_order_model.dart';
import 'package:burger_builder/providers/user_order_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'custom_stepper.dart';

class BuildControl extends StatefulWidget {
  **BuildControl({
    Key key,
    @required this.ingredient,
    @required this.currentValue,
  }) : super(key: key);**

  final IngredientsModel ingredient;
  final int currentValue;
  @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: () =>
                        Provider.of<UserOrderProvider>(context, listen: false)
                            .addIngredientHandler(widget.ingredient.name),
                    removeHandler: () =>
                        Provider.of<UserOrderProvider>(context, listen: false)
                            .removeIngredientHandler(widget.ingredient.name)),**
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Clean up file📄 custom_stepper.dart

.......
RoundedIconButton(
          icon: Icons.remove,
          iconSize: widget.iconSize,
          onPress: widget.value == widget.lowerLimit
              ? null
              : () {
                  _setValue();
                  widget.removeHandler();
                },
        ),

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

RoundedIconButton(
          icon: Icons.add,
          iconSize: widget.iconSize,
          onPress: widget.value == widget.upperLimit
              ? null
              : () {
                  _setValue();
                  widget.addHandler();
                },
        ),

Lets now work on the file📄 order_summary.dart , notice we are using Consumer

import 'package:burger_builder/helpers/app_constants.dart';
import 'package:burger_builder/providers/user_order_provider.dart';
import 'package:burger_builder/services/http_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class OrderSummary extends StatefulWidget {
  OrderSummary({Key key}) : super(key: key);
  @override
  _OrderSummaryState createState() => _OrderSummaryState();
}

class _OrderSummaryState extends State<OrderSummary> {
  bool visible = false;

  @override
  Widget build(BuildContext context) {
    **return Consumer<UserOrderProvider>(
        builder: (context, userOrderProvider, child) {**
      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:
                      **userOrderProvider.userOrderModel.userIngredients.length,**
                  separatorBuilder: (_, __) => Divider(height: 0.5),
                  itemBuilder: (BuildContext context, int index) {
                    var userIngredient =
                        **userOrderProvider.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 : \$' +
                    "${**userOrderProvider.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: () async {
                            setState(() {
                              visible = true;
                            });
                            var orderid = await HttpService().purchaseContinue(
                                Provider.of<UserOrderProvider>(context,
                                        listen: false)
                                    .userOrderModel);
                            if (orderid.length > 0) {
                              **Provider.of<UserOrderProvider>(context,
                                      listen: false)
                                  .setDummyData();**
                              SnackBar(
                                behavior: SnackBarBehavior.floating,
                                content: Text('order placed - ' + orderid),
                              );
                            }
                            setState(() {
                              visible = false;
                            });
                            Navigator.pop(context);
                          },
                        ),
                ],
              ),
            ],
          ),
        ),
      );
    });
  }
}

Tip:- Now we can even change our stateful widgets to stateless widgets

Additional info:

https://pub.dev/packages/peanut

https://itsallwidgets.com/burger-buildr

https://itsallwidgets.com/submit/30days

https://imageresizer.com/ 1080x1920


Tags

#Flutter#workshop
Previous Article
BurgerBuilder Flutter Workshop Part 1
Sumith Parambat Damodaran

Sumith Parambat Damodaran

Product Manager

Topics

General
Product Management
Technology

Related Posts

BurgerBuilder Flutter Workshop Part 1
February 25, 2021
4 min

Quick Links

Advertise with usAbout UsContact Us

Social Media