Burger Builder Flutter Workshop Part 2 - Provider

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