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
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
Quick Links
Legal Stuff