Building a Dynamic Shopping Cart Widget in Flutter with Discount Summary, Total Price, Checkout, and Promo Banner
A well-designed shopping cart is a cornerstone of any successful e-commerce application. It provides users with a clear overview of their selected items, enables easy quantity adjustments, displays applicable discounts, and guides them smoothly towards checkout. In this article, we will walk through the process of building a comprehensive shopping cart widget in Flutter, incorporating features like a discount summary, total price calculation, a checkout mechanism, and an engaging promo banner.
Core Components of Our Shopping Cart Widget
Our Flutter shopping cart will be composed of several key interactive and display elements:
- Product Data Models: To represent items and their quantities.
- Cart Management Logic: Using Provider for state management to handle adding, removing, and updating items.
- Cart Item List: Displaying each product with its quantity and individual price.
- Promo Banner: A visually appealing section to highlight ongoing offers.
- Discount Summary: An input field for promo codes and a clear display of applied discounts.
- Price Summary: Showing subtotal, discount amount, and the final total price.
- Checkout Button: To initiate the checkout process.
Setting Up the Project and Data Models
First, ensure you have Flutter installed. Create a new Flutter project:
flutter create shopping_cart_app
cd shopping_cart_app
Add the `provider` package to your `pubspec.yaml` file:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5 # Use the latest version
Then run `flutter pub get`.
Next, let's define our basic data models: `Product` and `CartItem`.
// models/product.dart
class Product {
final String id;
final String name;
final double price;
final String imageUrl;
Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
});
}
// models/cart_item.dart
class CartItem {
final Product product;
int quantity;
CartItem({required this.product, this.quantity = 1});
double get totalPrice => product.price * quantity;
}
Cart Management with Provider
We'll use a `ChangeNotifier` to manage the state of our shopping cart, allowing our UI to react to changes. This `CartProvider` will hold the list of items, handle additions, removals, quantity updates, and apply promo codes.
// providers/cart_provider.dart
import 'package:flutter/material.dart';
import '../models/product.dart';
import '../models/cart_item.dart';
class CartProvider extends ChangeNotifier {
Map _items = {};
String? _appliedPromoCode;
double _discountPercentage = 0.0; // e.g., 0.10 for 10% off
Map get items => {..._items}; // return a copy
int get itemCount => _items.length;
double get subtotal {
double total = 0.0;
_items.forEach((key, cartItem) {
total += cartItem.totalPrice;
});
return total;
}
double get discountAmount {
return subtotal * _discountPercentage;
}
double get total {
return subtotal - discountAmount;
}
String? get appliedPromoCode => _appliedPromoCode;
double get discountPercentage => _discountPercentage;
void addItem(Product product) {
if (_items.containsKey(product.id)) {
_items.update(
product.id,
(existingItem) => CartItem(
product: existingItem.product,
quantity: existingItem.quantity + 1,
),
);
} else {
_items.putIfAbsent(
product.id,
() => CartItem(product: product, quantity: 1),
);
}
notifyListeners();
}
void removeItem(String productId) {
_items.remove(productId);
notifyListeners();
}
void updateItemQuantity(String productId, int newQuantity) {
if (newQuantity <= 0) {
removeItem(productId);
} else {
_items.update(productId, (existingItem) =>
CartItem(product: existingItem.product, quantity: newQuantity));
}
notifyListeners();
}
// --- Promo Code Logic ---
void applyPromoCode(String promoCode) {
// A simple mock for promo code logic
if (promoCode.trim().toLowerCase() == 'save10') {
_discountPercentage = 0.10; // 10% off
_appliedPromoCode = promoCode;
} else if (promoCode.trim().toLowerCase() == 'free15') {
_discountPercentage = 0.15; // 15% off
_appliedPromoCode = promoCode;
} else {
_discountPercentage = 0.0;
_appliedPromoCode = null;
// You might want to show an error message here
}
notifyListeners();
}
void removePromoCode() {
_discountPercentage = 0.0;
_appliedPromoCode = null;
notifyListeners();
}
void clearCart() {
_items = {};
_discountPercentage = 0.0;
_appliedPromoCode = null;
notifyListeners();
}
}
Building the Cart Item List Widget
Each item in the cart needs to display its details and allow quantity adjustments. We'll create a `CartItemWidget` for this.
// widgets/cart_item_widget.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/cart_item.dart';
import '../providers/cart_provider.dart';
class CartItemWidget extends StatelessWidget {
final CartItem cartItem;
CartItemWidget(this.cartItem);
@override
Widget build(BuildContext context) {
final cart = Provider.of(context, listen: false);
return Card(
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
child: Padding(
padding: EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(cartItem.product.imageUrl),
),
title: Text(cartItem.product.name),
subtitle: Text('Price: \$${cartItem.product.price.toStringAsFixed(2)}'),
trailing: Container(
width: 120,
child: Row(
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
cart.updateItemQuantity(cartItem.product.id, cartItem.quantity - 1);
},
),
Text('${cartItem.quantity}'),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
cart.updateItemQuantity(cartItem.product.id, cartItem.quantity + 1);
},
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
cart.removeItem(cartItem.product.id);
},
),
],
),
),
),
),
);
}
}
Implementing Promo Banner and Discount Summary
We need a section for displaying promotional messages and another for users to input promo codes, along with a summary of the discount applied.
// widgets/promo_banner_widget.dart
import 'package:flutter/material.h';
class PromoBannerWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(15),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.lightBlue.shade100,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Icon(Icons.local_offer, color: Colors.blue),
SizedBox(width: 10),
Expanded(
child: Text(
'Use code SAVE10 for 10% off or FREE15 for 15% off your order!',
style: TextStyle(
color: Colors.blue.shade800,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
// widgets/discount_summary_widget.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
class DiscountSummaryWidget extends StatefulWidget {
@override
_DiscountSummaryWidgetState createState() => _DiscountSummaryWidgetState();
}
class _DiscountSummaryWidgetState extends State {
final TextEditingController _promoController = TextEditingController();
@override
void dispose() {
_promoController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final cart = Provider.of(context);
if (cart.appliedPromoCode != null) {
_promoController.text = cart.appliedPromoCode!;
}
return Card(
margin: EdgeInsets.all(15),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Promo Code', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Row(
children: [
Expanded(
child: TextField(
controller: _promoController,
decoration: InputDecoration(
hintText: 'Enter promo code',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
if (_promoController.text.isNotEmpty) {
cart.applyPromoCode(_promoController.text);
}
},
child: Text('Apply'),
),
],
),
if (cart.appliedPromoCode != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${cart.appliedPromoCode} (${(cart.discountPercentage * 100).toStringAsFixed(0)}% off)',
style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold),
),
InkWell(
onTap: () {
cart.removePromoCode();
_promoController.clear();
},
child: Text('Remove', style: TextStyle(color: Colors.red)),
),
],
),
),
],
),
),
);
}
}
Calculating and Displaying Total Price
This widget will summarize the subtotal, discount, and final total.
// widgets/price_summary_widget.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
class PriceSummaryWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = Provider.of(context);
return Card(
margin: EdgeInsets.all(15),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
_buildPriceRow('Subtotal', cart.subtotal),
_buildPriceRow('Discount', -cart.discountAmount, isDiscount: true),
Divider(),
_buildPriceRow('Total', cart.total, isTotal: true),
],
),
),
);
}
Widget _buildPriceRow(String title, double amount, {bool isTotal = false, bool isDiscount = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: isTotal ? 20 : 16,
fontWeight: isTotal ? FontWeight.bold : FontWeight.normal,
color: isDiscount ? Colors.red.shade700 : Colors.black,
),
),
Text(
'\$${amount.toStringAsFixed(2)}',
style: TextStyle(
fontSize: isTotal ? 20 : 16,
fontWeight: isTotal ? FontWeight.bold : FontWeight.normal,
color: isDiscount ? Colors.red.shade700 : Colors.black,
),
),
],
),
);
}
}
The Checkout Button
A simple button to trigger the checkout process. In a real application, this would navigate to a payment screen or confirm the order.
// widgets/checkout_button_widget.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
class CheckoutButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = Provider.of(context);
return Padding(
padding: const EdgeInsets.all(15.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: cart.itemCount > 0
? () {
// Implement your checkout logic here
// For example, navigate to a payment screen or confirm order.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Proceeding to checkout with total \$${cart.total.toStringAsFixed(2)}')),
);
// Optionally clear the cart after checkout
// cart.clearCart();
}
: null, // Disable button if cart is empty
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 15),
backgroundColor: Colors.blue, // Use primary or accent color
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text('Checkout'),
),
),
);
}
}
Putting It All Together: The Main Cart Screen
Finally, let's assemble all these widgets into our main `ShoppingCartScreen`.
// screens/shopping_cart_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
import '../widgets/cart_item_widget.dart';
import '../widgets/promo_banner_widget.dart';
import '../widgets/discount_summary_widget.dart';
import '../widgets/price_summary_widget.dart';
import '../widgets/checkout_button_widget.dart';
import '../models/product.dart'; // Import Product model
class ShoppingCartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = Provider.of(context);
return Scaffold(
appBar: AppBar(
title: Text('My Shopping Cart'),
),
body: Column(
children: [
PromoBannerWidget(), // Display the promo banner
Expanded(
child: cart.itemCount == 0
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.shopping_cart_outlined, size: 80, color: Colors.grey),
SizedBox(height: 10),
Text('Your cart is empty!', style: TextStyle(fontSize: 20, color: Colors.grey)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Example: Add some dummy products to cart for testing
cart.addItem(Product(id: 'p1', name: 'Laptop Pro', price: 1200.0, imageUrl: 'https://via.placeholder.com/150/0000FF/FFFFFF?text=Laptop'));
cart.addItem(Product(id: 'p2', name: 'Wireless Mouse', price: 25.0, imageUrl: 'https://via.placeholder.com/150/FF0000/FFFFFF?text=Mouse'));
cart.addItem(Product(id: 'p3', name: 'USB-C Hub', price: 50.0, imageUrl: 'https://via.placeholder.com/150/00FF00/FFFFFF?text=Hub'));
},
child: Text('Add Some Products'),
)
],
),
)
: ListView.builder(
itemCount: cart.itemCount,
itemBuilder: (ctx, i) {
final cartItem = cart.items.values.toList()[i];
return CartItemWidget(cartItem);
},
),
),
if (cart.itemCount > 0)
Column(
children: [
DiscountSummaryWidget(), // Promo code input and discount display
PriceSummaryWidget(), // Subtotal, discount, and total
CheckoutButtonWidget(), // Checkout button
],
),
],
),
);
}
}
Integrating with Your App
Finally, modify your `main.dart` to use the `CartProvider` and display the `ShoppingCartScreen`.
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './providers/cart_provider.dart';
import './screens/shopping_cart_screen.dart';
import './models/product.dart'; // Import Product model if needed for initial items
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (ctx) => CartProvider(),
child: MaterialApp(
title: 'Flutter Shopping Cart',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ShoppingCartScreen(),
debugShowCheckedModeBanner: false,
),
);
}
}
Conclusion
You have now successfully built a comprehensive and dynamic shopping cart widget in Flutter. This solution incorporates essential e-commerce features such as state management with Provider, clear display of cart items, an interactive promo banner, a discount application mechanism, a transparent price summary, and a functional checkout button. This robust foundation can be further extended with features like persistent storage, different discount types, shipping calculations, and integration with payment gateways to create a full-fledged e-commerce experience.